mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-17 12:56:41 +02:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 23788674c8 | |||
| 121eb24e73 | |||
| 571d60182a | |||
| 167a9dcaf3 | |||
| 37327259cb | |||
| cdb25656d5 | |||
| 25c876caa2 | |||
| cf83e31f23 | |||
| 3bc238b58b | |||
| b8de69dced | |||
| e7fcb692a4 | |||
| dae38574ab |
@@ -7,3 +7,4 @@
|
||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"name": "opencode",
|
||||
"devDependencies": {
|
||||
"prettier": "3.5.3",
|
||||
"sst": "3.17.6",
|
||||
"sst": "3.17.8",
|
||||
},
|
||||
},
|
||||
"packages/function": {
|
||||
@@ -31,6 +31,7 @@
|
||||
"@openauthjs/openauth": "0.4.3",
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"ai": "catalog:",
|
||||
"air": "0.4.14",
|
||||
"decimal.js": "10.5.0",
|
||||
"diff": "8.0.2",
|
||||
"env-paths": "3.0.0",
|
||||
@@ -462,7 +463,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.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
|
||||
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||
|
||||
@@ -492,6 +493,8 @@
|
||||
|
||||
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
||||
|
||||
"@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
|
||||
|
||||
"@types/turndown": ["@types/turndown@5.0.5", "", {}, "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w=="],
|
||||
@@ -514,6 +517,8 @@
|
||||
|
||||
"ai": ["ai@4.3.16", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g=="],
|
||||
|
||||
"air": ["air@0.4.14", "", { "dependencies": { "zephyr": "~1.3.5" } }, "sha512-E8bl9LlSGSQqjxxjeGIrpYpf8jVyJplsdK1bTobh61F7ks+3aLeXL4KbGSJIFsiaSSz5ZExLU51DGztmQSlZTQ=="],
|
||||
|
||||
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
@@ -602,7 +607,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.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
||||
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
|
||||
|
||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||
|
||||
@@ -1476,23 +1481,23 @@
|
||||
|
||||
"split2": ["split2@3.2.2", "", { "dependencies": { "readable-stream": "^3.0.0" } }, "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg=="],
|
||||
|
||||
"sst": ["sst@3.17.6", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.6", "sst-darwin-x64": "3.17.6", "sst-linux-arm64": "3.17.6", "sst-linux-x64": "3.17.6", "sst-linux-x86": "3.17.6", "sst-win32-arm64": "3.17.6", "sst-win32-x64": "3.17.6", "sst-win32-x86": "3.17.6" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-p+AcqwfYQUdkxeRjCikQoTMviPCBiGoU7M0vcV6GDVmVis8hzhVw4EFfHTafZC+aWfy1Ke2UQi66vZlEVWuEqA=="],
|
||||
"sst": ["sst@3.17.8", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.8", "sst-darwin-x64": "3.17.8", "sst-linux-arm64": "3.17.8", "sst-linux-x64": "3.17.8", "sst-linux-x86": "3.17.8", "sst-win32-arm64": "3.17.8", "sst-win32-x64": "3.17.8", "sst-win32-x86": "3.17.8" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-P/a9/ZsjtQRrTBerBMO1ODaVa5HVTmNLrQNJiYvu2Bgd0ov+vefQeHv6oima8HLlPwpDIPS2gxJk8BZrTZMfCA=="],
|
||||
|
||||
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6tb7KlcPR7PTi3ofQv8dX/n6Jf7pNP9VfrnYL4HBWnWrcYaZeJ5MWobILfIJ/y2jHgoqmg9e5C3266Eds0JQyw=="],
|
||||
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-50P6YRMnZVItZUfB0+NzqMww2mmm4vB3zhTVtWUtGoXeiw78g1AEnVlmS28gYXPHM1P987jTvR7EON9u9ig/Dg=="],
|
||||
|
||||
"sst-darwin-x64": ["sst-darwin-x64@3.17.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-lFakq6/EgTuBSjbl8Kry4pfgAPEIyn6o7ZkyRz3hz5331wUaX88yfjs3tL9JQ8Ey6jBUYxwhP/Q1n7fzIG046g=="],
|
||||
"sst-darwin-x64": ["sst-darwin-x64@3.17.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-P0pnMHCmpkpcsxkWpilmeoD79LkbkoIcv6H0aeM9ArT/71/JBhvqH+HjMHSJCzni/9uR6er+nH5F+qol0UO6Bw=="],
|
||||
|
||||
"sst-linux-arm64": ["sst-linux-arm64@3.17.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-SdTxXMbTEdiwOqp37w31kXv97vHqSx3oK9h/76lKg7V9k5JxPJ6JMefPLhoKWwK0Zh6AndY2zo2oRoEv4SIaDw=="],
|
||||
"sst-linux-arm64": ["sst-linux-arm64@3.17.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-vun54YA/UzprCu9p8BC4rMwFU5Cj9xrHAHYLYUp/yq4H0pfmBIiQM62nsfIKizRThe/TkBFy60EEi9myf6raYA=="],
|
||||
|
||||
"sst-linux-x64": ["sst-linux-x64@3.17.6", "", { "os": "linux", "cpu": "x64" }, "sha512-qneh7uWDiTUYx8X1Y3h2YVw3SJ0ybBBlRrVybIvCM09JqQ8+qq/XjKXGzA/3/EF0Jr7Ug8cARSn9CwxhdQGN7Q=="],
|
||||
"sst-linux-x64": ["sst-linux-x64@3.17.8", "", { "os": "linux", "cpu": "x64" }, "sha512-HqByCaLE2gEJbM20P1QRd+GqDMAiieuU53FaZA1F+AGxQi+kR82NWjrPqFcMj4dMYg8w/TWXuV+G5+PwoUmpDw=="],
|
||||
|
||||
"sst-linux-x86": ["sst-linux-x86@3.17.6", "", { "os": "linux", "cpu": "none" }, "sha512-pU3D5OeqnmfxGqN31DxuwWnc1OayxhkErnITHhZ39D0MTiwbIgCapH26FuLW8B08/uxJWG8djUlOboCRhSBvWA=="],
|
||||
"sst-linux-x86": ["sst-linux-x86@3.17.8", "", { "os": "linux", "cpu": "none" }, "sha512-bCd6QM3MejfSmdvg8I/k+aUJQIZEQJg023qmN78fv00vwlAtfECvY7tjT9E2m3LDp33pXrcRYbFOQzPu+tWFfA=="],
|
||||
|
||||
"sst-win32-arm64": ["sst-win32-arm64@3.17.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-Rr3RTYWAsH9sM9CbM/sAZCk7dB1OsSAljjJuuHMvdSAYW3RDpXEza0PBJGxnBID2eOrpswEchzMPL2d8LtL7oA=="],
|
||||
"sst-win32-arm64": ["sst-win32-arm64@3.17.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-pilx0n8gm4aHJae/vNiqIwZkWF3tdwWzD/ON7hkytw+CVSZ0FXtyFW/yO/+2u3Yw0Kj0lSWPnUqYgm/eHPLwQA=="],
|
||||
|
||||
"sst-win32-x64": ["sst-win32-x64@3.17.6", "", { "os": "win32", "cpu": "x64" }, "sha512-yZ3roxwI0Wve9PFzdrrF1kfzCmIMFCCoa8qKeXY7LxCJ4QQIqHbCOccLK1Wv/MIU/mcZHWXTQVCLHw77uaa0GQ=="],
|
||||
"sst-win32-x64": ["sst-win32-x64@3.17.8", "", { "os": "win32", "cpu": "x64" }, "sha512-Jb0FVRyiOtESudF1V8ucW65PuHrx/iOHUamIO0JnbujWNHZBTRPB2QHN1dbewgkueYDaCmyS8lvuIImLwYJnzQ=="],
|
||||
|
||||
"sst-win32-x86": ["sst-win32-x86@3.17.6", "", { "os": "win32", "cpu": "none" }, "sha512-zV7TJWPJN9PmIXr15iXFSs0tbGsa52oBR3+xiKrUj2qj9XsZe7HBFwskRnHyiFq0durZY9kk9ZtoVlpuUuzr1g=="],
|
||||
"sst-win32-x86": ["sst-win32-x86@3.17.8", "", { "os": "win32", "cpu": "none" }, "sha512-oVmFa/PoElQmfnGJlB0w6rPXiYuldiagO6AbrLMT/6oAnWerLQ8Uhv9tJWfMh3xtPLImQLTjxDo1v0AIzEv9QA=="],
|
||||
|
||||
"stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="],
|
||||
|
||||
@@ -1698,6 +1703,8 @@
|
||||
|
||||
"youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
|
||||
|
||||
"zephyr": ["zephyr@1.3.6", "", {}, "sha512-oYH52DGZzIbXNrkijskaR8YpVKnXAe8jNgH1KirglVBnTFOn6mK9/0SVCxGn+73l0Hjhr4UYNzYkO07LXSWy6w=="],
|
||||
|
||||
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||
|
||||
"zod-openapi": ["zod-openapi@4.2.4", "", { "peerDependencies": { "zod": "^3.21.4" } }, "sha512-tsrQpbpqFCXqVXUzi3TPwFhuMtLN3oNZobOtYnK6/5VkXsNdnIgyNr4r8no4wmYluaxzN3F7iS+8xCW8BmMQ8g=="],
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.2.14",
|
||||
"scripts": {
|
||||
"dev": "bun run packages/opencode/src/index.ts",
|
||||
"typecheck": "bun run --filter='*' typecheck",
|
||||
"stainless": "bun run ./packages/opencode/src/index.ts serve ",
|
||||
"postinstall": "./scripts/hooks"
|
||||
@@ -22,7 +23,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "3.5.3",
|
||||
"sst": "3.17.6"
|
||||
"sst": "3.17.8"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
node_modules
|
||||
research
|
||||
dist
|
||||
gen
|
||||
|
||||
@@ -9,6 +9,7 @@ import fs from "fs/promises"
|
||||
import { Installation } from "../../installation"
|
||||
import { Config } from "../../config/config"
|
||||
import { Bus } from "../../bus"
|
||||
import { Log } from "../../util/log"
|
||||
|
||||
export const TuiCommand = cmd({
|
||||
command: "$0 [project]",
|
||||
@@ -57,6 +58,9 @@ export const TuiCommand = cmd({
|
||||
cwd = process.cwd()
|
||||
cmd = [binary]
|
||||
}
|
||||
Log.Default.info("tui", {
|
||||
cmd,
|
||||
})
|
||||
const proc = Bun.spawn({
|
||||
cmd: [...cmd, ...process.argv.slice(2)],
|
||||
cwd,
|
||||
|
||||
@@ -92,11 +92,20 @@ export namespace LSPClient {
|
||||
},
|
||||
}),
|
||||
5_000,
|
||||
).catch(() => {
|
||||
throw new InitializeError({ serverID })
|
||||
).catch((err) => {
|
||||
log.error("initialize error", { error: err })
|
||||
throw new InitializeError(
|
||||
{ serverID },
|
||||
{
|
||||
cause: err,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
await connection.sendNotification("initialized", {})
|
||||
log.info("initialized")
|
||||
log.info("initialized", {
|
||||
serverID,
|
||||
})
|
||||
|
||||
const files: {
|
||||
[path: string]: number
|
||||
@@ -174,7 +183,6 @@ export namespace LSPClient {
|
||||
log.info("shutting down", { serverID })
|
||||
connection.end()
|
||||
connection.dispose()
|
||||
server.process.kill("SIGTERM")
|
||||
log.info("shutdown", { serverID })
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export namespace LSP {
|
||||
const handle = await server.spawn(App.info())
|
||||
if (!handle) break
|
||||
const client = await LSPClient.create(server.id, handle).catch(
|
||||
() => {},
|
||||
(err) => log.error("", { error: err }),
|
||||
)
|
||||
if (!client) break
|
||||
clients.set(server.id, client)
|
||||
|
||||
@@ -4,6 +4,8 @@ import path from "path"
|
||||
import { Global } from "../global"
|
||||
import { Log } from "../util/log"
|
||||
import { BunProc } from "../bun"
|
||||
import { $ } from "bun"
|
||||
import fs from "fs/promises"
|
||||
|
||||
export namespace LSPServer {
|
||||
const log = Log.create({ service: "lsp.server" })
|
||||
@@ -144,4 +146,60 @@ export namespace LSPServer {
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const ElixirLS: Info = {
|
||||
id: "elixir-ls",
|
||||
extensions: [".ex", ".exs"],
|
||||
async spawn() {
|
||||
let binary = Bun.which("elixir-ls")
|
||||
if (!binary) {
|
||||
const elixirLsPath = path.join(Global.Path.bin, "elixir-ls")
|
||||
binary = path.join(
|
||||
Global.Path.bin,
|
||||
"elixir-ls-master",
|
||||
"release",
|
||||
process.platform === "win32"
|
||||
? "language_server.bar"
|
||||
: "language_server.sh",
|
||||
)
|
||||
|
||||
if (!(await Bun.file(binary).exists())) {
|
||||
const elixir = Bun.which("elixir")
|
||||
if (!elixir) {
|
||||
log.error("elixir is required to run elixir-ls")
|
||||
return
|
||||
}
|
||||
|
||||
log.info("downloading elixir-ls from GitHub releases")
|
||||
|
||||
const response = await fetch(
|
||||
"https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip",
|
||||
)
|
||||
if (!response.ok) return
|
||||
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
|
||||
await Bun.file(zipPath).write(response)
|
||||
|
||||
await $`unzip -o -q ${zipPath}`.cwd(Global.Path.bin).nothrow()
|
||||
|
||||
await fs.rm(zipPath, {
|
||||
force: true,
|
||||
recursive: true,
|
||||
})
|
||||
|
||||
await $`mix deps.get && mix compile && mix elixir_ls.release2 -o release`
|
||||
.quiet()
|
||||
.cwd(path.join(Global.Path.bin, "elixir-ls-master"))
|
||||
.env({ MIX_ENV: "prod", ...process.env })
|
||||
|
||||
log.info(`installed elixir-ls`, {
|
||||
path: elixirLsPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
process: spawn(binary),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import { App } from "../app/app"
|
||||
import {
|
||||
add,
|
||||
commit,
|
||||
init,
|
||||
checkout,
|
||||
statusMatrix,
|
||||
remove,
|
||||
} from "isomorphic-git"
|
||||
import { $ } from "bun"
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
import fs from "fs/promises"
|
||||
import { Ripgrep } from "../file/ripgrep"
|
||||
import { Log } from "../util/log"
|
||||
|
||||
@@ -16,66 +9,55 @@ export namespace Snapshot {
|
||||
const log = Log.create({ service: "snapshot" })
|
||||
|
||||
export async function create(sessionID: string) {
|
||||
return
|
||||
log.info("creating snapshot")
|
||||
const app = App.info()
|
||||
const git = gitdir(sessionID)
|
||||
const files = await Ripgrep.files({
|
||||
cwd: app.path.cwd,
|
||||
limit: app.git ? undefined : 1000,
|
||||
})
|
||||
// not a git repo and too big to snapshot
|
||||
if (!app.git && files.length === 1000) return
|
||||
await init({
|
||||
dir: app.path.cwd,
|
||||
gitdir: git,
|
||||
fs,
|
||||
})
|
||||
const status = await statusMatrix({
|
||||
fs,
|
||||
gitdir: git,
|
||||
dir: app.path.cwd,
|
||||
})
|
||||
await add({
|
||||
fs,
|
||||
gitdir: git,
|
||||
parallel: true,
|
||||
dir: app.path.cwd,
|
||||
filepath: files,
|
||||
})
|
||||
for (const [file, _head, workdir, stage] of status) {
|
||||
if (workdir === 0 && stage === 1) {
|
||||
log.info("remove", { file })
|
||||
await remove({
|
||||
fs,
|
||||
gitdir: git,
|
||||
dir: app.path.cwd,
|
||||
filepath: file,
|
||||
})
|
||||
}
|
||||
|
||||
// not a git repo, check if too big to snapshot
|
||||
if (!app.git) {
|
||||
const files = await Ripgrep.files({
|
||||
cwd: app.path.cwd,
|
||||
limit: 1000,
|
||||
})
|
||||
log.info("found files", { count: files.length })
|
||||
if (files.length > 1000) return
|
||||
}
|
||||
const result = await commit({
|
||||
fs,
|
||||
gitdir: git,
|
||||
dir: app.path.cwd,
|
||||
message: "snapshot",
|
||||
author: {
|
||||
name: "opencode",
|
||||
email: "mail@opencode.ai",
|
||||
},
|
||||
})
|
||||
log.info("commit", { result })
|
||||
return result
|
||||
|
||||
if (await fs.mkdir(git, { recursive: true })) {
|
||||
await $`git init`
|
||||
.env({
|
||||
...process.env,
|
||||
GIT_DIR: git,
|
||||
GIT_WORK_TREE: app.path.root,
|
||||
})
|
||||
.quiet()
|
||||
.nothrow()
|
||||
log.info("initialized")
|
||||
}
|
||||
|
||||
await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
|
||||
log.info("added files")
|
||||
|
||||
const result =
|
||||
await $`git --git-dir ${git} commit --allow-empty -m "snapshot" --author="opencode <mail@opencode.ai>"`
|
||||
.quiet()
|
||||
.cwd(app.path.cwd)
|
||||
.nothrow()
|
||||
log.info("commit")
|
||||
|
||||
const match = result.stdout.toString().match(/\[.+ ([a-f0-9]+)\]/)
|
||||
if (!match) return
|
||||
return match![1]
|
||||
}
|
||||
|
||||
export async function restore(sessionID: string, commit: string) {
|
||||
log.info("restore", { commit })
|
||||
const app = App.info()
|
||||
await checkout({
|
||||
fs,
|
||||
gitdir: gitdir(sessionID),
|
||||
dir: app.path.cwd,
|
||||
ref: commit,
|
||||
force: true,
|
||||
})
|
||||
const git = gitdir(sessionID)
|
||||
await $`git --git-dir=${git} checkout ${commit} --force`
|
||||
.quiet()
|
||||
.cwd(app.path.root)
|
||||
}
|
||||
|
||||
function gitdir(sessionID: string) {
|
||||
|
||||
@@ -489,10 +489,10 @@ export function replace(
|
||||
BlockAnchorReplacer,
|
||||
WhitespaceNormalizedReplacer,
|
||||
IndentationFlexibleReplacer,
|
||||
EscapeNormalizedReplacer,
|
||||
TrimmedBoundaryReplacer,
|
||||
ContextAwareReplacer,
|
||||
MultiOccurrenceReplacer,
|
||||
// EscapeNormalizedReplacer,
|
||||
// TrimmedBoundaryReplacer,
|
||||
// ContextAwareReplacer,
|
||||
// MultiOccurrenceReplacer,
|
||||
]) {
|
||||
for (const search of replacer(content, oldString)) {
|
||||
const index = content.indexOf(search)
|
||||
|
||||
@@ -36,6 +36,10 @@ if (isDocs) {
|
||||
}
|
||||
---
|
||||
|
||||
{ slug === "" && (
|
||||
<title>{title} | AI coding agent built for the terminal</title>
|
||||
)}
|
||||
|
||||
<Default {...Astro.props}><slot /></Default>
|
||||
|
||||
{ (isDocs || !slug.startsWith("s")) && (
|
||||
|
||||
@@ -321,6 +321,7 @@ function TextPart(props: TextPartProps) {
|
||||
|
||||
createEffect(() => {
|
||||
local.text
|
||||
local.expand
|
||||
setTimeout(checkOverflow, 0)
|
||||
})
|
||||
|
||||
@@ -372,6 +373,7 @@ function ErrorPart(props: ErrorPartProps) {
|
||||
|
||||
createEffect(() => {
|
||||
local.children
|
||||
local.expand
|
||||
setTimeout(checkOverflow, 0)
|
||||
})
|
||||
|
||||
@@ -425,6 +427,7 @@ function MarkdownPart(props: MarkdownPartProps) {
|
||||
|
||||
createEffect(() => {
|
||||
local.text
|
||||
local.expand
|
||||
setTimeout(checkOverflow, 0)
|
||||
})
|
||||
|
||||
@@ -486,6 +489,14 @@ function TerminalPart(props: TerminalPartProps) {
|
||||
}
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
local.command
|
||||
local.result
|
||||
local.error
|
||||
local.expand
|
||||
setTimeout(checkOverflow, 0)
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
checkOverflow()
|
||||
window.addEventListener("resize", checkOverflow)
|
||||
@@ -597,6 +608,8 @@ export default function Share(props: {
|
||||
}) {
|
||||
let lastScrollY = 0
|
||||
let scrollTimeout: number | undefined
|
||||
let scrollSentinel: HTMLElement | undefined
|
||||
let scrollObserver: IntersectionObserver | undefined
|
||||
|
||||
const id = props.id
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
@@ -604,6 +617,7 @@ export default function Share(props: {
|
||||
|
||||
const [showScrollButton, setShowScrollButton] = createSignal(false)
|
||||
const [isButtonHovered, setIsButtonHovered] = createSignal(false)
|
||||
const [isNearBottom, setIsNearBottom] = createSignal(false)
|
||||
|
||||
const [store, setStore] = createStore<{
|
||||
info?: Session.Info
|
||||
@@ -713,10 +727,9 @@ export default function Share(props: {
|
||||
const currentScrollY = window.scrollY
|
||||
const isScrollingDown = currentScrollY > lastScrollY
|
||||
const scrolled = currentScrollY > 200 // Show after scrolling 200px
|
||||
const isNearBottom = window.innerHeight + currentScrollY >= document.body.scrollHeight - 100
|
||||
|
||||
// Only show when scrolling down, scrolled enough, and not near bottom
|
||||
const shouldShow = isScrollingDown && scrolled && !isNearBottom
|
||||
const shouldShow = isScrollingDown && scrolled && !isNearBottom()
|
||||
|
||||
// Update last scroll position
|
||||
lastScrollY = currentScrollY
|
||||
@@ -732,7 +745,7 @@ export default function Share(props: {
|
||||
if (!isButtonHovered()) {
|
||||
setShowScrollButton(false)
|
||||
}
|
||||
}, 3000)
|
||||
}, 1500)
|
||||
} else if (!isButtonHovered()) {
|
||||
// Only hide if not hovered (to prevent disappearing while user is about to click)
|
||||
setShowScrollButton(false)
|
||||
@@ -744,6 +757,26 @@ export default function Share(props: {
|
||||
|
||||
onMount(() => {
|
||||
lastScrollY = window.scrollY // Initialize scroll position
|
||||
|
||||
// Create sentinel element
|
||||
const sentinel = document.createElement("div")
|
||||
sentinel.style.height = "1px"
|
||||
sentinel.style.position = "absolute"
|
||||
sentinel.style.bottom = "100px"
|
||||
sentinel.style.width = "100%"
|
||||
sentinel.style.pointerEvents = "none"
|
||||
document.body.appendChild(sentinel)
|
||||
|
||||
// Create intersection observer
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
setIsNearBottom(entries[0].isIntersecting)
|
||||
})
|
||||
observer.observe(sentinel)
|
||||
|
||||
// Store references for cleanup
|
||||
scrollSentinel = sentinel
|
||||
scrollObserver = observer
|
||||
|
||||
checkScrollNeed()
|
||||
window.addEventListener("scroll", checkScrollNeed)
|
||||
window.addEventListener("resize", checkScrollNeed)
|
||||
@@ -752,6 +785,15 @@ export default function Share(props: {
|
||||
onCleanup(() => {
|
||||
window.removeEventListener("scroll", checkScrollNeed)
|
||||
window.removeEventListener("resize", checkScrollNeed)
|
||||
|
||||
// Clean up observer and sentinel
|
||||
if (scrollObserver) {
|
||||
scrollObserver.disconnect()
|
||||
}
|
||||
if (scrollSentinel) {
|
||||
document.body.removeChild(scrollSentinel)
|
||||
}
|
||||
|
||||
if (scrollTimeout) {
|
||||
clearTimeout(scrollTimeout)
|
||||
}
|
||||
@@ -855,7 +897,6 @@ export default function Share(props: {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -865,7 +906,7 @@ export default function Share(props: {
|
||||
fallback={<p>Waiting for messages...</p>}
|
||||
>
|
||||
<div class={styles.parts}>
|
||||
<SuspenseList>
|
||||
<SuspenseList revealOrder="forwards">
|
||||
<For each={data().messages}>
|
||||
{(msg, msgIndex) => (
|
||||
<Suspense>
|
||||
@@ -880,8 +921,11 @@ export default function Share(props: {
|
||||
)
|
||||
return null
|
||||
|
||||
const anchor = createMemo(() => `${msg.id}-${partIndex()}`)
|
||||
const [showResults, setShowResults] = createSignal(false)
|
||||
const anchor = createMemo(
|
||||
() => `${msg.id}-${partIndex()}`,
|
||||
)
|
||||
const [showResults, setShowResults] =
|
||||
createSignal(false)
|
||||
const isLastPart = createMemo(
|
||||
() =>
|
||||
data().messages.length === msgIndex() + 1 &&
|
||||
@@ -903,7 +947,9 @@ export default function Share(props: {
|
||||
const duration = DateTime.fromMillis(
|
||||
metadata?.time.end || 0,
|
||||
)
|
||||
.diff(DateTime.fromMillis(metadata?.time.start || 0))
|
||||
.diff(
|
||||
DateTime.fromMillis(metadata?.time.start || 0),
|
||||
)
|
||||
.toMillis()
|
||||
|
||||
return { metadata, args, result, duration }
|
||||
@@ -911,7 +957,12 @@ export default function Share(props: {
|
||||
|
||||
onMount(() => {
|
||||
const hash = window.location.hash.slice(1)
|
||||
if (hash !== "" && hash === anchor()) {
|
||||
// Wait till all parts are loaded
|
||||
if (
|
||||
hash !== ""
|
||||
&& msg.parts.length === partIndex() + 1
|
||||
&& data().messages.length === msgIndex() + 1
|
||||
) {
|
||||
scrollToAnchor(hash)
|
||||
}
|
||||
})
|
||||
@@ -921,7 +972,9 @@ export default function Share(props: {
|
||||
{/* User text */}
|
||||
<Match
|
||||
when={
|
||||
msg.role === "user" && part.type === "text" && part
|
||||
msg.role === "user" &&
|
||||
part.type === "text" &&
|
||||
part
|
||||
}
|
||||
>
|
||||
{(part) => (
|
||||
@@ -972,7 +1025,9 @@ export default function Share(props: {
|
||||
expand={isLastPart()}
|
||||
text={stripEnclosingTag(part().text)}
|
||||
/>
|
||||
<Show when={isLastPart() && data().completed}>
|
||||
<Show
|
||||
when={isLastPart() && data().completed}
|
||||
>
|
||||
<span
|
||||
data-part-footer
|
||||
title={DateTime.fromMillis(
|
||||
@@ -1041,7 +1096,8 @@ export default function Share(props: {
|
||||
}
|
||||
>
|
||||
{(_part) => {
|
||||
const matches = () => toolData()?.metadata?.matches
|
||||
const matches = () =>
|
||||
toolData()?.metadata?.matches
|
||||
const splitArgs = () => {
|
||||
const { pattern, ...rest } = toolData()?.args
|
||||
return { pattern, rest }
|
||||
@@ -1066,11 +1122,14 @@ export default function Share(props: {
|
||||
<div data-part-tool-body>
|
||||
<div data-part-title>
|
||||
<span data-element-label>Grep</span>
|
||||
<b>“{splitArgs().pattern}”</b>
|
||||
<b>
|
||||
“{splitArgs().pattern}”
|
||||
</b>
|
||||
</div>
|
||||
<Show
|
||||
when={
|
||||
Object.keys(splitArgs().rest).length > 0
|
||||
Object.keys(splitArgs().rest)
|
||||
.length > 0
|
||||
}
|
||||
>
|
||||
<div data-part-tool-args>
|
||||
@@ -1299,8 +1358,10 @@ export default function Share(props: {
|
||||
data().rootDir,
|
||||
),
|
||||
)
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const preview = () => toolData()?.metadata?.preview
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
const preview = () =>
|
||||
toolData()?.metadata?.preview
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -1333,7 +1394,9 @@ export default function Share(props: {
|
||||
</div>
|
||||
</Match>
|
||||
{/* Always try to show CodeBlock if preview is available (even if empty string) */}
|
||||
<Match when={typeof preview() === 'string'}>
|
||||
<Match
|
||||
when={typeof preview() === "string"}
|
||||
>
|
||||
<div data-part-tool-result>
|
||||
<ResultsButton
|
||||
showCopy="Show preview"
|
||||
@@ -1346,7 +1409,9 @@ export default function Share(props: {
|
||||
<Show when={showResults()}>
|
||||
<div data-part-tool-code>
|
||||
<CodeBlock
|
||||
lang={getShikiLang(filePath())}
|
||||
lang={getShikiLang(
|
||||
filePath(),
|
||||
)}
|
||||
code={preview()}
|
||||
/>
|
||||
</div>
|
||||
@@ -1354,7 +1419,12 @@ export default function Share(props: {
|
||||
</div>
|
||||
</Match>
|
||||
{/* Fallback to TextPart if preview is not a string (e.g. undefined) AND result exists */}
|
||||
<Match when={typeof preview() !== 'string' && toolData()?.result}>
|
||||
<Match
|
||||
when={
|
||||
typeof preview() !== "string" &&
|
||||
toolData()?.result
|
||||
}
|
||||
>
|
||||
<div data-part-tool-result>
|
||||
<ResultsButton
|
||||
results={showResults()}
|
||||
@@ -1398,7 +1468,8 @@ export default function Share(props: {
|
||||
data().rootDir,
|
||||
),
|
||||
)
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
const content = () => toolData()?.args?.content
|
||||
const diagnostics = createMemo(() =>
|
||||
getDiagnostics(
|
||||
@@ -1415,7 +1486,10 @@ export default function Share(props: {
|
||||
>
|
||||
<div data-section="decoration">
|
||||
<AnchorIcon id={anchor()}>
|
||||
<IconDocumentPlus width={18} height={18} />
|
||||
<IconDocumentPlus
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</AnchorIcon>
|
||||
<div></div>
|
||||
</div>
|
||||
@@ -1435,7 +1509,7 @@ export default function Share(props: {
|
||||
<div data-part-tool-result>
|
||||
<ErrorPart>
|
||||
{formatErrorString(
|
||||
toolData()?.result
|
||||
toolData()?.result,
|
||||
)}
|
||||
</ErrorPart>
|
||||
</div>
|
||||
@@ -1453,8 +1527,12 @@ export default function Share(props: {
|
||||
<Show when={showResults()}>
|
||||
<div data-part-tool-code>
|
||||
<CodeBlock
|
||||
lang={getShikiLang(filePath())}
|
||||
code={toolData()?.args?.content}
|
||||
lang={getShikiLang(
|
||||
filePath(),
|
||||
)}
|
||||
code={
|
||||
toolData()?.args?.content
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
@@ -1481,8 +1559,10 @@ export default function Share(props: {
|
||||
>
|
||||
{(_part) => {
|
||||
const diff = () => toolData()?.metadata?.diff
|
||||
const message = () => toolData()?.metadata?.message
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const message = () =>
|
||||
toolData()?.metadata?.message
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
const filePath = createMemo(() =>
|
||||
stripWorkingDirectory(
|
||||
toolData()?.args.filePath,
|
||||
@@ -1504,7 +1584,10 @@ export default function Share(props: {
|
||||
>
|
||||
<div data-section="decoration">
|
||||
<AnchorIcon id={anchor()}>
|
||||
<IconPencilSquare width={18} height={18} />
|
||||
<IconPencilSquare
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</AnchorIcon>
|
||||
<div></div>
|
||||
</div>
|
||||
@@ -1527,7 +1610,9 @@ export default function Share(props: {
|
||||
<Match when={diff()}>
|
||||
<div data-part-tool-edit>
|
||||
<DiffView
|
||||
class={styles["diff-code-block"]}
|
||||
class={
|
||||
styles["diff-code-block"]
|
||||
}
|
||||
diff={diff()}
|
||||
lang={getShikiLang(filePath())}
|
||||
/>
|
||||
@@ -1556,9 +1641,12 @@ export default function Share(props: {
|
||||
}
|
||||
>
|
||||
{(_part) => {
|
||||
const command = () => toolData()?.metadata?.title
|
||||
const desc = () => toolData()?.metadata?.description
|
||||
const result = () => toolData()?.metadata?.stdout
|
||||
const command = () =>
|
||||
toolData()?.metadata?.title
|
||||
const desc = () =>
|
||||
toolData()?.metadata?.description
|
||||
const result = () =>
|
||||
toolData()?.metadata?.stdout
|
||||
const error = () => toolData()?.metadata?.stderr
|
||||
|
||||
return (
|
||||
@@ -1569,7 +1657,10 @@ export default function Share(props: {
|
||||
>
|
||||
<div data-section="decoration">
|
||||
<AnchorIcon id={anchor()}>
|
||||
<IconCommandLine width={18} height={18} />
|
||||
<IconCommandLine
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</AnchorIcon>
|
||||
<div></div>
|
||||
</div>
|
||||
@@ -1604,7 +1695,9 @@ export default function Share(props: {
|
||||
>
|
||||
{(_part) => {
|
||||
const todos = createMemo(() =>
|
||||
sortTodosByStatus(toolData()?.args?.todos ?? []),
|
||||
sortTodosByStatus(
|
||||
toolData()?.args?.todos ?? [],
|
||||
),
|
||||
)
|
||||
const starting = () =>
|
||||
todos().every((t) => t.status === "pending")
|
||||
@@ -1670,7 +1763,8 @@ export default function Share(props: {
|
||||
{(_part) => {
|
||||
const url = () => toolData()?.args.url
|
||||
const format = () => toolData()?.args.format
|
||||
const hasError = () => toolData()?.metadata?.error
|
||||
const hasError = () =>
|
||||
toolData()?.metadata?.error
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -1793,7 +1887,8 @@ export default function Share(props: {
|
||||
</Match>
|
||||
<Match
|
||||
when={
|
||||
part().toolInvocation.state === "call"
|
||||
part().toolInvocation.state ===
|
||||
"call"
|
||||
}
|
||||
>
|
||||
<TextPart
|
||||
@@ -1839,7 +1934,10 @@ export default function Share(props: {
|
||||
</Match>
|
||||
|
||||
<Match when={msg.role === "user"}>
|
||||
<IconUserCircle width={18} height={18} />
|
||||
<IconUserCircle
|
||||
width={18}
|
||||
height={18}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
</AnchorIcon>
|
||||
@@ -1848,7 +1946,9 @@ export default function Share(props: {
|
||||
<div data-section="content">
|
||||
<div data-part-tool-body>
|
||||
<div data-part-title>
|
||||
<span data-element-label>{part.type}</span>
|
||||
<span data-element-label>
|
||||
{part.type}
|
||||
</span>
|
||||
</div>
|
||||
<TextPart
|
||||
text={JSON.stringify(part, null, 2)}
|
||||
|
||||
Reference in New Issue
Block a user