diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml
index 824733901d6b..568543eb3915 100644
--- a/.github/workflows/stats.yml
+++ b/.github/workflows/stats.yml
@@ -31,5 +31,3 @@ jobs:
git add STATS.md
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
git push
- env:
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
diff --git a/.opencode/agent/gemini.md b/.opencode/agent/gemini.md
new file mode 100644
index 000000000000..b6b29124f1b0
--- /dev/null
+++ b/.opencode/agent/gemini.md
@@ -0,0 +1,9 @@
+---
+description: Delegates tasks to the local Gemini CLI agent
+color: "#8E75FF"
+tools:
+ - gemini
+---
+
+You are a proxy agent for the Gemini CLI.
+When the user gives you a task, you MUST use the `gemini` tool to delegate the task to the Gemini CLI. Do not attempt to complete the task yourself using other tools. Pass the user's prompt directly into the tool. Wait for the tool to complete and then summarize the results for the user.
\ No newline at end of file
diff --git a/.opencode/tool/gemini.ts b/.opencode/tool/gemini.ts
new file mode 100644
index 000000000000..d64de9303f3f
--- /dev/null
+++ b/.opencode/tool/gemini.ts
@@ -0,0 +1,30 @@
+///
+import { tool } from "@opencode-ai/plugin"
+import { spawn } from "child_process"
+
+export default tool({
+ description: "Delegates a task to the Gemini CLI agent. Gemini will autonomously research, edit files, and run commands to complete the task.",
+ args: {
+ prompt: tool.schema.string().describe("The complete task description to send to Gemini."),
+ },
+ async execute(args) {
+ return new Promise((resolve, reject) => {
+ // Spawns the gemini CLI
+ const proc = spawn("gemini", ["-p", args.prompt], {
+ stdio: "pipe",
+ env: process.env,
+ })
+
+ let output = ""
+ proc.stdout.on("data", (data) => (output += data.toString()))
+ proc.stderr.on("data", (data) => (output += data.toString()))
+
+ proc.on("close", (code) => {
+ resolve(`Gemini exited with code ${code}.\n\nOutput:\n${output}`)
+ })
+ proc.on("error", (err) => {
+ reject(`Failed to start Gemini CLI: ${err.message}`)
+ })
+ })
+ },
+})
\ No newline at end of file
diff --git a/packages/console/app/src/component/email-signup.tsx b/packages/console/app/src/component/email-signup.tsx
index 65f81b5fc6df..568a6e06d5a4 100644
--- a/packages/console/app/src/component/email-signup.tsx
+++ b/packages/console/app/src/component/email-signup.tsx
@@ -5,19 +5,6 @@ import { Show } from "solid-js"
const emailSignup = action(async (formData: FormData) => {
"use server"
- const emailAddress = formData.get("email")!
- const listId = "8b9bb82c-9d5f-11f0-975f-0df6fd1e4945"
- const response = await fetch(`https://api.emailoctopus.com/lists/${listId}/contacts`, {
- method: "PUT",
- headers: {
- Authorization: `Bearer ${Resource.EMAILOCTOPUS_API_KEY.value}`,
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- email_address: emailAddress,
- }),
- })
- console.log(response)
return true
})
diff --git a/packages/console/function/src/log-processor.ts b/packages/console/function/src/log-processor.ts
index 9e76e2ceb085..e733162dba19 100644
--- a/packages/console/function/src/log-processor.ts
+++ b/packages/console/function/src/log-processor.ts
@@ -38,18 +38,6 @@ export default {
}
}
console.log(JSON.stringify(metrics, null, 2))
-
- const ret = await fetch("https://api.honeycomb.io/1/events/zen", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- "X-Honeycomb-Event-Time": (event.eventTimestamp ?? Date.now()).toString(),
- "X-Honeycomb-Team": Resource.HONEYCOMB_API_KEY.value,
- },
- body: JSON.stringify(metrics),
- })
- console.log(ret.status)
- console.log(await ret.text())
}
},
}
diff --git a/packages/function/src/api.ts b/packages/function/src/api.ts
index 54b93ad715a2..90f11c703f4e 100644
--- a/packages/function/src/api.ts
+++ b/packages/function/src/api.ts
@@ -12,262 +12,8 @@ type Env = {
WEB_DOMAIN: string
}
-async function getFeishuTenantToken(): Promise {
- const response = await fetch("https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- app_id: Resource.FEISHU_APP_ID.value,
- app_secret: Resource.FEISHU_APP_SECRET.value,
- }),
- })
- const data = (await response.json()) as { tenant_access_token?: string }
- if (!data.tenant_access_token) throw new Error("Failed to get Feishu tenant token")
- return data.tenant_access_token
-}
-
-export class SyncServer extends DurableObject {
- constructor(ctx: DurableObjectState, env: Env) {
- super(ctx, env)
- }
- async fetch() {
- console.log("SyncServer subscribe")
-
- const webSocketPair = new WebSocketPair()
- const [client, server] = Object.values(webSocketPair)
-
- this.ctx.acceptWebSocket(server)
-
- const data = await this.ctx.storage.list()
- Array.from(data.entries())
- .filter(([key, _]) => key.startsWith("session/"))
- .map(([key, content]) => server.send(JSON.stringify({ key, content })))
-
- return new Response(null, {
- status: 101,
- webSocket: client,
- })
- }
-
- async webSocketMessage(ws, message) {}
-
- async webSocketClose(ws, code, reason, wasClean) {
- ws.close(code, "Durable Object is closing WebSocket")
- }
-
- async publish(key: string, content: any) {
- const sessionID = await this.getSessionID()
- if (
- !key.startsWith(`session/info/${sessionID}`) &&
- !key.startsWith(`session/message/${sessionID}/`) &&
- !key.startsWith(`session/part/${sessionID}/`)
- )
- return new Response("Error: Invalid key", { status: 400 })
-
- // store message
- await this.env.Bucket.put(`share/${key}.json`, JSON.stringify(content), {
- httpMetadata: {
- contentType: "application/json",
- },
- })
- await this.ctx.storage.put(key, content)
- const clients = this.ctx.getWebSockets()
- console.log("SyncServer publish", key, "to", clients.length, "subscribers")
- for (const client of clients) {
- client.send(JSON.stringify({ key, content }))
- }
- }
-
- public async share(sessionID: string) {
- let secret = await this.getSecret()
- if (secret) return secret
- secret = randomUUID()
-
- await this.ctx.storage.put("secret", secret)
- await this.ctx.storage.put("sessionID", sessionID)
-
- return secret
- }
-
- public async getData() {
- const data = (await this.ctx.storage.list()) as Map
- return Array.from(data.entries())
- .filter(([key, _]) => key.startsWith("session/"))
- .map(([key, content]) => ({ key, content }))
- }
-
- public async assertSecret(secret: string) {
- if (secret !== (await this.getSecret())) throw new Error("Invalid secret")
- }
-
- private async getSecret() {
- return this.ctx.storage.get("secret")
- }
-
- private async getSessionID() {
- return this.ctx.storage.get("sessionID")
- }
-
- async clear() {
- const sessionID = await this.getSessionID()
- const list = await this.env.Bucket.list({
- prefix: `session/message/${sessionID}/`,
- limit: 1000,
- })
- for (const item of list.objects) {
- await this.env.Bucket.delete(item.key)
- }
- await this.env.Bucket.delete(`session/info/${sessionID}`)
- await this.ctx.storage.deleteAll()
- }
-
- static shortName(id: string) {
- return id.substring(id.length - 8)
- }
-}
-
export default new Hono<{ Bindings: Env }>()
.get("/", (c) => c.text("Hello, world!"))
- .post("/share_create", async (c) => {
- const body = await c.req.json<{ sessionID: string }>()
- const sessionID = body.sessionID
- const short = SyncServer.shortName(sessionID)
- const id = c.env.SYNC_SERVER.idFromName(short)
- const stub = c.env.SYNC_SERVER.get(id)
- const secret = await stub.share(sessionID)
- return c.json({
- secret,
- url: `https://${c.env.WEB_DOMAIN}/s/${short}`,
- })
- })
- .post("/share_delete", async (c) => {
- const body = await c.req.json<{ sessionID: string; secret: string }>()
- const sessionID = body.sessionID
- const secret = body.secret
- const id = c.env.SYNC_SERVER.idFromName(SyncServer.shortName(sessionID))
- const stub = c.env.SYNC_SERVER.get(id)
- await stub.assertSecret(secret)
- await stub.clear()
- return c.json({})
- })
- .post("/share_delete_admin", async (c) => {
- const body = await c.req.json<{ sessionShortName: string; adminSecret: string }>()
- const sessionShortName = body.sessionShortName
- const adminSecret = body.adminSecret
- if (adminSecret !== Resource.ADMIN_SECRET.value) throw new Error("Invalid admin secret")
- const id = c.env.SYNC_SERVER.idFromName(sessionShortName)
- const stub = c.env.SYNC_SERVER.get(id)
- await stub.clear()
- return c.json({})
- })
- .post("/share_sync", async (c) => {
- const body = await c.req.json<{
- sessionID: string
- secret: string
- key: string
- content: any
- }>()
- const name = SyncServer.shortName(body.sessionID)
- const id = c.env.SYNC_SERVER.idFromName(name)
- const stub = c.env.SYNC_SERVER.get(id)
- await stub.assertSecret(body.secret)
- await stub.publish(body.key, body.content)
- return c.json({})
- })
- .get("/share_poll", async (c) => {
- const upgradeHeader = c.req.header("Upgrade")
- if (!upgradeHeader || upgradeHeader !== "websocket") {
- return c.text("Error: Upgrade header is required", { status: 426 })
- }
- const id = c.req.query("id")
- console.log("share_poll", id)
- if (!id) return c.text("Error: Share ID is required", { status: 400 })
- const stub = c.env.SYNC_SERVER.get(c.env.SYNC_SERVER.idFromName(id))
- return stub.fetch(c.req.raw)
- })
- .get("/share_data", async (c) => {
- const id = c.req.query("id")
- console.log("share_data", id)
- if (!id) return c.text("Error: Share ID is required", { status: 400 })
- const stub = c.env.SYNC_SERVER.get(c.env.SYNC_SERVER.idFromName(id))
- const data = await stub.getData()
-
- let info
- const messages: Record = {}
- data.forEach((d) => {
- const [root, type, ...splits] = d.key.split("/")
- if (root !== "session") return
- if (type === "info") {
- info = d.content
- return
- }
- if (type === "message") {
- messages[d.content.id] = {
- parts: [],
- ...d.content,
- }
- }
- if (type === "part") {
- messages[d.content.messageID].parts.push(d.content)
- }
- })
-
- return c.json({ info, messages })
- })
- .post("/feishu", async (c) => {
- const body = (await c.req.json()) as {
- challenge?: string
- event?: {
- message?: {
- message_id?: string
- root_id?: string
- parent_id?: string
- chat_id?: string
- content?: string
- }
- }
- }
- console.log(JSON.stringify(body, null, 2))
- const challenge = body.challenge
- if (challenge) return c.json({ challenge })
-
- const content = body.event?.message?.content
- const parsed =
- typeof content === "string" && content.trim().startsWith("{")
- ? (JSON.parse(content) as {
- text?: string
- })
- : undefined
- const text = typeof parsed?.text === "string" ? parsed.text : typeof content === "string" ? content : ""
-
- let message = text.trim().replace(/^@_user_\d+\s*/, "")
- message = message.replace(/^aiden,?\s*/i, "<@759257817772851260> ")
- if (!message) return c.json({ ok: true })
-
- const threadId = body.event?.message?.root_id || body.event?.message?.message_id
- if (threadId) message = `${message} [${threadId}]`
-
- const response = await fetch(
- `https://discord.com/api/v10/channels/${Resource.DISCORD_SUPPORT_CHANNEL_ID.value}/messages`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bot ${Resource.DISCORD_SUPPORT_BOT_TOKEN.value}`,
- },
- body: JSON.stringify({
- content: `${message}`,
- }),
- },
- )
-
- if (!response.ok) {
- console.error(await response.text())
- return c.json({ error: "Discord bot message failed" }, { status: 502 })
- }
-
- return c.json({ ok: true })
- })
/**
* Used by the GitHub action to get GitHub installation access token given the OIDC token
*/
diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts
index 2b44308f1301..9aeef703b27e 100644
--- a/packages/opencode/src/agent/agent.ts
+++ b/packages/opencode/src/agent/agent.ts
@@ -284,12 +284,6 @@ export namespace Agent {
const existing = await list()
const params = {
- experimental_telemetry: {
- isEnabled: cfg.experimental?.openTelemetry,
- metadata: {
- userId: cfg.username ?? "unknown",
- },
- },
temperature: 0.3,
messages: [
...system.map(
diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts
index 9d7e8c56171e..2c8580c8cd37 100644
--- a/packages/opencode/src/cli/cmd/import.ts
+++ b/packages/opencode/src/cli/cmd/import.ts
@@ -31,48 +31,17 @@ export const ImportCommand = cmd({
const isUrl = args.file.startsWith("http://") || args.file.startsWith("https://")
if (isUrl) {
- const urlMatch = args.file.match(/https?:\/\/opncd\.ai\/share\/([a-zA-Z0-9_-]+)/)
- if (!urlMatch) {
- process.stdout.write(`Invalid URL format. Expected: https://opncd.ai/share/`)
- process.stdout.write(EOL)
- return
- }
-
- const slug = urlMatch[1]
- const response = await fetch(`https://opncd.ai/api/share/${slug}`)
-
- if (!response.ok) {
- process.stdout.write(`Failed to fetch share data: ${response.statusText}`)
- process.stdout.write(EOL)
- return
- }
-
- const data = await response.json()
-
- if (!data.info || !data.messages || Object.keys(data.messages).length === 0) {
- process.stdout.write(`Share not found: ${slug}`)
- process.stdout.write(EOL)
- return
- }
+ process.stdout.write(`Importing from URLs is disabled for privacy.`)
+ process.stdout.write(EOL)
+ return
+ }
- exportData = {
- info: data.info,
- messages: Object.values(data.messages).map((msg: any) => {
- const { parts, ...info } = msg
- return {
- info,
- parts,
- }
- }),
- }
- } else {
- const file = Bun.file(args.file)
- exportData = await file.json().catch(() => {})
- if (!exportData) {
- process.stdout.write(`File not found: ${args.file}`)
- process.stdout.write(EOL)
- return
- }
+ const file = Bun.file(args.file)
+ exportData = await file.json().catch(() => {})
+ if (!exportData) {
+ process.stdout.write(`File not found: ${args.file}`)
+ process.stdout.write(EOL)
+ return
}
if (!exportData) {
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 020e626cba89..b5f733bb0d92 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -1071,10 +1071,6 @@ export namespace Config {
chatMaxRetries: z.number().optional().describe("Number of retries for chat completions on failure"),
disable_paste_summary: z.boolean().optional(),
batch_tool: z.boolean().optional().describe("Enable the batch tool"),
- openTelemetry: z
- .boolean()
- .optional()
- .describe("Enable OpenTelemetry spans for AI SDK calls (using the 'experimental_telemetry' flag)"),
primary_tools: z
.array(z.string())
.optional()
diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts
index d106c2d86e9b..e677113e08ec 100644
--- a/packages/opencode/src/flag/flag.ts
+++ b/packages/opencode/src/flag/flag.ts
@@ -9,12 +9,12 @@ export namespace Flag {
export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"]
export declare const OPENCODE_CONFIG_DIR: string | undefined
export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"]
- export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
+ export const OPENCODE_DISABLE_AUTOUPDATE = true
export const OPENCODE_DISABLE_PRUNE = truthy("OPENCODE_DISABLE_PRUNE")
export const OPENCODE_DISABLE_TERMINAL_TITLE = truthy("OPENCODE_DISABLE_TERMINAL_TITLE")
export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"]
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
- export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")
+ export const OPENCODE_DISABLE_LSP_DOWNLOAD = true
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
export const OPENCODE_DISABLE_MODELS_FETCH = truthy("OPENCODE_DISABLE_MODELS_FETCH")
diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts
index d18c9e31a13b..4eba0ea1e645 100644
--- a/packages/opencode/src/installation/index.ts
+++ b/packages/opencode/src/installation/index.ts
@@ -184,63 +184,6 @@ export namespace Installation {
export const USER_AGENT = `opencode/${CHANNEL}/${VERSION}/${Flag.OPENCODE_CLIENT}`
export async function latest(installMethod?: Method) {
- const detectedMethod = installMethod || (await method())
-
- if (detectedMethod === "brew") {
- const formula = await getBrewFormula()
- if (formula === "opencode") {
- return fetch("https://formulae.brew.sh/api/formula/opencode.json")
- .then((res) => {
- if (!res.ok) throw new Error(res.statusText)
- return res.json()
- })
- .then((data: any) => data.versions.stable)
- }
- }
-
- if (detectedMethod === "npm" || detectedMethod === "bun" || detectedMethod === "pnpm") {
- const registry = await iife(async () => {
- const r = (await $`npm config get registry`.quiet().nothrow().text()).trim()
- const reg = r || "https://registry.npmjs.org"
- return reg.endsWith("/") ? reg.slice(0, -1) : reg
- })
- const channel = CHANNEL
- return fetch(`${registry}/opencode-ai/${channel}`)
- .then((res) => {
- if (!res.ok) throw new Error(res.statusText)
- return res.json()
- })
- .then((data: any) => data.version)
- }
-
- if (detectedMethod === "choco") {
- return fetch(
- "https://community.chocolatey.org/api/v2/Packages?$filter=Id%20eq%20%27opencode%27%20and%20IsLatestVersion&$select=Version",
- { headers: { Accept: "application/json;odata=verbose" } },
- )
- .then((res) => {
- if (!res.ok) throw new Error(res.statusText)
- return res.json()
- })
- .then((data: any) => data.d.results[0].Version)
- }
-
- if (detectedMethod === "scoop") {
- return fetch("https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/opencode.json", {
- headers: { Accept: "application/json" },
- })
- .then((res) => {
- if (!res.ok) throw new Error(res.statusText)
- return res.json()
- })
- .then((data: any) => data.version)
- }
-
- return fetch("https://api.github.com/repos/anomalyco/opencode/releases/latest")
- .then((res) => {
- if (!res.ok) throw new Error(res.statusText)
- return res.json()
- })
- .then((data: any) => data.tag_name.replace(/^v/, ""))
+ return VERSION
}
}
diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts
index 55c9c4524737..dd2c4e5243b7 100644
--- a/packages/opencode/src/session/llm.ts
+++ b/packages/opencode/src/session/llm.ts
@@ -260,7 +260,6 @@ export namespace LLM {
extractReasoningMiddleware({ tagName: "think", startWithReasoning: false }),
],
}),
- experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
})
}
diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts
index dddce95cb4f9..3c7fc583d900 100644
--- a/packages/opencode/src/share/share-next.ts
+++ b/packages/opencode/src/share/share-next.ts
@@ -15,7 +15,7 @@ export namespace ShareNext {
return Config.get().then((x) => x.enterprise?.url ?? "https://opncd.ai")
}
- const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1"
+ const disabled = true
export async function init() {
if (disabled) return
diff --git a/packages/opencode/src/share/share.ts b/packages/opencode/src/share/share.ts
index f7bf4b3fa52a..a7f8baf9ebcc 100644
--- a/packages/opencode/src/share/share.ts
+++ b/packages/opencode/src/share/share.ts
@@ -70,7 +70,7 @@ export namespace Share {
process.env["OPENCODE_API"] ??
(Installation.isPreview() || Installation.isLocal() ? "https://api.dev.opencode.ai" : "https://api.opencode.ai")
- const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1"
+ const disabled = true
export async function create(sessionID: string) {
if (disabled) return { url: "", secret: "" }
diff --git a/script/stats.ts b/script/stats.ts
index 318b590af7c9..b277d203f4c9 100755
--- a/script/stats.ts
+++ b/script/stats.ts
@@ -201,16 +201,6 @@ console.log(`Fetched npm all-time downloads: ${npmDownloads.toLocaleString()}\n`
await save(githubTotal, npmDownloads)
-await sendToPostHog("download", {
- count: githubTotal,
- source: "github",
-})
-
-await sendToPostHog("download", {
- count: npmDownloads,
- source: "npm",
-})
-
const totalDownloads = githubTotal + npmDownloads
console.log("=".repeat(60))