diff --git a/bun.lock b/bun.lock index 4164ba1c..013c7839 100644 --- a/bun.lock +++ b/bun.lock @@ -143,6 +143,7 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", + "chokidar": "4.0.3", "decimal.js": "10.5.0", "diff": "8.0.2", "gray-matter": "4.0.3", diff --git a/opencode.json b/opencode.json index 2fa64407..720ece5c 100644 --- a/opencode.json +++ b/opencode.json @@ -1,9 +1,3 @@ { - "$schema": "https://opencode.ai/config.json", - "mcp": { - "weather": { - "type": "local", - "command": ["opencode", "x", "@h1deya/mcp-server-weather"] - } - } + "$schema": "https://opencode.ai/config.json" } diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 3ba05429..5bf59f26 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -37,6 +37,7 @@ "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", + "chokidar": "4.0.3", "decimal.js": "10.5.0", "diff": "8.0.2", "gray-matter": "4.0.3", diff --git a/packages/opencode/src/cli/cmd/tui.ts b/packages/opencode/src/cli/cmd/tui.ts index e0fe4901..7281f6e4 100644 --- a/packages/opencode/src/cli/cmd/tui.ts +++ b/packages/opencode/src/cli/cmd/tui.ts @@ -10,7 +10,7 @@ import { Installation } from "../../installation" import { Config } from "../../config/config" import { Bus } from "../../bus" import { Log } from "../../util/log" -import { FileWatcher } from "../../file/watch" +import { FileWatcher } from "../../file/watcher" import { Ide } from "../../ide" import { Flag } from "../../flag/flag" @@ -101,7 +101,6 @@ export const TuiCommand = cmd({ } return undefined })() - FileWatcher.init() const providers = await Provider.list() if (Object.keys(providers).length === 0) { return "needs_provider" @@ -181,6 +180,7 @@ export const TuiCommand = cmd({ .then(() => Bus.publish(Ide.Event.Installed, { ide })) .catch(() => {}) })() + FileWatcher.init() await proc.exited server.stop() diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 74413657..7d86845e 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -365,6 +365,11 @@ export namespace Config { .record(z.string(), Command) .optional() .describe("Command configuration, see https://opencode.ai/docs/commands"), + watcher: z + .object({ + ignore: z.array(z.string()).optional(), + }) + .optional(), plugin: z.string().array().optional(), snapshot: z.boolean().optional(), share: z diff --git a/packages/opencode/src/file/ignore.ts b/packages/opencode/src/file/ignore.ts new file mode 100644 index 00000000..53e2003b --- /dev/null +++ b/packages/opencode/src/file/ignore.ts @@ -0,0 +1,61 @@ +export namespace FileIgnore { + const DEFAULT_PATTERNS = [ + // Dependencies + "**/node_modules/**", + "**/bower_components/**", + "**/.pnpm-store/**", + "**/vendor/**", + + // vcs + "**/.git/**", + + // Build outputs + "**/dist/**", + "**/build/**", + "**/out/**", + "**/.next/**", + "**/target/**", // Rust + "**/bin/**", + "**/obj/**", // .NET + + // Version control + "**/.git/**", + "**/.svn/**", + "**/.hg/**", + + // IDE/Editor + "**/.vscode/**", + "**/.idea/**", + "**/*.swp", + "**/*.swo", + + // OS + "**/.DS_Store", + "**/Thumbs.db", + + // Logs & temp + "**/logs/**", + "**/tmp/**", + "**/temp/**", + "**/*.log", + + // Coverage/test outputs + "**/coverage/**", + "**/.nyc_output/**", + ] + + const GLOBS = DEFAULT_PATTERNS.map((p) => new Bun.Glob(p)) + + export function match( + filepath: string, + opts: { + extra?: Bun.Glob[] + }, + ) { + const extra = opts.extra || [] + for (const glob of [...GLOBS, ...extra]) { + if (glob.match(filepath)) return true + } + return false + } +} diff --git a/packages/opencode/src/file/watch.ts b/packages/opencode/src/file/watch.ts deleted file mode 100644 index 526b29c9..00000000 --- a/packages/opencode/src/file/watch.ts +++ /dev/null @@ -1,46 +0,0 @@ -import z from "zod/v4" -import { Bus } from "../bus" -import fs from "fs" -import { Log } from "../util/log" -import { Flag } from "../flag/flag" -import { Instance } from "../project/instance" - -export namespace FileWatcher { - const log = Log.create({ service: "file.watcher" }) - - export const Event = { - Updated: Bus.event( - "file.watcher.updated", - z.object({ - file: z.string(), - event: z.union([z.literal("rename"), z.literal("change")]), - }), - ), - } - const state = Instance.state( - () => { - if (Instance.project.vcs !== "git") return {} - try { - const watcher = fs.watch(Instance.directory, { recursive: true }, (event, file) => { - log.info("change", { file, event }) - if (!file) return - Bus.publish(Event.Updated, { - file, - event, - }) - }) - return { watcher } - } catch { - return {} - } - }, - async (state) => { - state.watcher?.close() - }, - ) - - export function init() { - if (Flag.OPENCODE_DISABLE_WATCHER || true) return - state() - } -} diff --git a/packages/opencode/src/file/watcher.ts b/packages/opencode/src/file/watcher.ts new file mode 100644 index 00000000..fddff515 --- /dev/null +++ b/packages/opencode/src/file/watcher.ts @@ -0,0 +1,61 @@ +import z from "zod/v4" +import { Bus } from "../bus" +import chokidar from "chokidar" +import { Flag } from "../flag/flag" +import { Instance } from "../project/instance" +import { Log } from "../util/log" +import { FileIgnore } from "./ignore" +import { Config } from "../config/config" + +export namespace FileWatcher { + const log = Log.create({ service: "file.watcher" }) + + export const Event = { + Updated: Bus.event( + "file.watcher.updated", + z.object({ + file: z.string(), + event: z.union([z.literal("add"), z.literal("change"), z.literal("unlink")]), + }), + ), + } + + const state = Instance.state( + async () => { + if (Instance.project.vcs !== "git") return {} + log.info("init") + const cfg = await Config.get() + const ignore = (cfg.watcher?.ignore ?? []).map((v) => new Bun.Glob(v)) + const watcher = chokidar.watch(Instance.directory, { + ignoreInitial: true, + awaitWriteFinish: true, + ignored: (filepath) => { + return FileIgnore.match(filepath, { + extra: ignore, + }) + }, + }) + watcher.on("change", (file) => { + Bus.publish(Event.Updated, { file, event: "change" }) + }) + watcher.on("add", (file) => { + Bus.publish(Event.Updated, { file, event: "add" }) + }) + watcher.on("unlink", (file) => { + Bus.publish(Event.Updated, { file, event: "unlink" }) + }) + watcher.on("ready", () => { + log.info("ready") + }) + return { watcher } + }, + async (state) => { + state.watcher?.close() + }, + ) + + export function init() { + if (!Flag.OPENCODE_EXPERIMENTAL_WATCHER) return + state() + } +} diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 418f3228..a0537907 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -1,6 +1,5 @@ export namespace Flag { export const OPENCODE_AUTO_SHARE = truthy("OPENCODE_AUTO_SHARE") - export const OPENCODE_DISABLE_WATCHER = truthy("OPENCODE_DISABLE_WATCHER") export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"] export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"] export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE") @@ -10,6 +9,9 @@ export namespace Flag { export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS") export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT") + // Experimental + export const OPENCODE_EXPERIMENTAL_WATCHER = truthy("OPENCODE_EXPERIMENTAL_WATCHER") + function truthy(key: string) { const value = process.env[key]?.toLowerCase() return value === "true" || value === "1" diff --git a/packages/sdk/go/.release-please-manifest.json b/packages/sdk/go/.release-please-manifest.json index ed21d28c..727e2bea 100644 --- a/packages/sdk/go/.release-please-manifest.json +++ b/packages/sdk/go/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.13.0" + ".": "0.14.0" } diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml index 7f4a9c11..9d47e52e 100644 --- a/packages/sdk/go/.stats.yml +++ b/packages/sdk/go/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-2e754dafcad0636137256cef499b2bcd72cf17de08f44ec03c3589b2a05341a2.yml -openapi_spec_hash: 2d3cf84d3033068ce6c07386411527ef +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-0a4165f1eabf826d3092ea6b789aa527048278dcd4bd891f9e5ac890b9bcbb35.yml +openapi_spec_hash: da60e4fc813eb0f9ac3ab5f112e26bf6 config_hash: 026ef000d34bf2f930e7b41e77d2d3ff diff --git a/packages/sdk/go/CHANGELOG.md b/packages/sdk/go/CHANGELOG.md index 01801706..9e13db9e 100644 --- a/packages/sdk/go/CHANGELOG.md +++ b/packages/sdk/go/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.14.0 (2025-09-14) + +Full Changelog: [v0.13.0...v0.14.0](https://github.com/sst/opencode-sdk-go/compare/v0.13.0...v0.14.0) + +### Features + +- **api:** api update ([dad0bc3](https://github.com/sst/opencode-sdk-go/commit/dad0bc3da99f20a0d002a6b94e049fb70f8e6a77)) + ## 0.13.0 (2025-09-14) Full Changelog: [v0.12.0...v0.13.0](https://github.com/sst/opencode-sdk-go/compare/v0.12.0...v0.13.0) diff --git a/packages/sdk/go/README.md b/packages/sdk/go/README.md index 2c48f53c..96898209 100644 --- a/packages/sdk/go/README.md +++ b/packages/sdk/go/README.md @@ -24,7 +24,7 @@ Or to pin the version: ```sh -go get -u 'github.com/sst/opencode-sdk-go@v0.13.0' +go get -u 'github.com/sst/opencode-sdk-go@v0.14.0' ``` diff --git a/packages/sdk/go/internal/version.go b/packages/sdk/go/internal/version.go index 871f0965..870e575a 100644 --- a/packages/sdk/go/internal/version.go +++ b/packages/sdk/go/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.13.0" // x-release-please-version +const PackageVersion = "0.14.0" // x-release-please-version diff --git a/packages/sdk/go/sessionpermission.go b/packages/sdk/go/sessionpermission.go index 4d49bd87..4dbfe1d2 100644 --- a/packages/sdk/go/sessionpermission.go +++ b/packages/sdk/go/sessionpermission.go @@ -8,12 +8,15 @@ import ( "fmt" "net/http" "net/url" + "reflect" "github.com/sst/opencode-sdk-go/internal/apijson" "github.com/sst/opencode-sdk-go/internal/apiquery" "github.com/sst/opencode-sdk-go/internal/param" "github.com/sst/opencode-sdk-go/internal/requestconfig" "github.com/sst/opencode-sdk-go/option" + "github.com/sst/opencode-sdk-go/shared" + "github.com/tidwall/gjson" ) // SessionPermissionService contains methods and other services that help with @@ -60,7 +63,7 @@ type Permission struct { Title string `json:"title,required"` Type string `json:"type,required"` CallID string `json:"callID"` - Pattern string `json:"pattern"` + Pattern PermissionPatternUnion `json:"pattern"` JSON permissionJSON `json:"-"` } @@ -107,6 +110,30 @@ func (r permissionTimeJSON) RawJSON() string { return r.raw } +// Union satisfied by [shared.UnionString] or [PermissionPatternArray]. +type PermissionPatternUnion interface { + ImplementsPermissionPatternUnion() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*PermissionPatternUnion)(nil)).Elem(), + "", + apijson.UnionVariant{ + TypeFilter: gjson.String, + Type: reflect.TypeOf(shared.UnionString("")), + }, + apijson.UnionVariant{ + TypeFilter: gjson.JSON, + Type: reflect.TypeOf(PermissionPatternArray{}), + }, + ) +} + +type PermissionPatternArray []string + +func (r PermissionPatternArray) ImplementsPermissionPatternUnion() {} + type SessionPermissionRespondParams struct { Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"` Directory param.Field[string] `query:"directory"` diff --git a/packages/sdk/go/shared/union.go b/packages/sdk/go/shared/union.go index 91c73305..e01f54c8 100644 --- a/packages/sdk/go/shared/union.go +++ b/packages/sdk/go/shared/union.go @@ -2,6 +2,10 @@ package shared +type UnionString string + +func (UnionString) ImplementsPermissionPatternUnion() {} + type UnionBool bool func (UnionBool) ImplementsConfigProviderOptionsTimeoutUnion() {}