summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-07-01 12:06:38 -0400
committerDax Raad <[email protected]>2025-07-01 12:28:34 -0400
commit11d042be25ee0509db323dc117724b0ac9e4610a (patch)
treef9b457eb5c8ea7aca8c293fd0208ce1f30310629 /packages
parent33b5fe236a204a3d1d935a94e1d1d5f3a6f312a8 (diff)
downloadopencode-11d042be25ee0509db323dc117724b0ac9e4610a.tar.gz
opencode-11d042be25ee0509db323dc117724b0ac9e4610a.zip
snapshot functionality
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/package.json1
-rw-r--r--packages/opencode/src/cli/cmd/debug/file.ts26
-rw-r--r--packages/opencode/src/cli/cmd/debug/index.ts17
-rw-r--r--packages/opencode/src/cli/cmd/debug/lsp.ts37
-rw-r--r--packages/opencode/src/cli/cmd/debug/ripgrep.ts (renamed from packages/opencode/src/cli/cmd/debug.ts)69
-rw-r--r--packages/opencode/src/cli/cmd/debug/snapshot.ts39
-rw-r--r--packages/opencode/src/snapshot/index.ts85
7 files changed, 210 insertions, 64 deletions
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index 6ccf2f969..fae2e3425 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -37,6 +37,7 @@
"env-paths": "3.0.0",
"hono": "4.7.10",
"hono-openapi": "0.4.8",
+ "isomorphic-git": "1.32.1",
"open": "10.1.2",
"remeda": "2.22.3",
"ts-lsp-client": "1.0.3",
diff --git a/packages/opencode/src/cli/cmd/debug/file.ts b/packages/opencode/src/cli/cmd/debug/file.ts
new file mode 100644
index 000000000..1d23ed851
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/debug/file.ts
@@ -0,0 +1,26 @@
+import { File } from "../../../file"
+import { bootstrap } from "../../bootstrap"
+import { cmd } from "../cmd"
+import path from "path"
+
+export const FileCommand = cmd({
+ command: "file",
+ builder: (yargs) => yargs.command(FileReadCommand).demandCommand(),
+ async handler() {},
+})
+
+const FileReadCommand = cmd({
+ command: "read <path>",
+ builder: (yargs) =>
+ yargs.positional("path", {
+ type: "string",
+ demandOption: true,
+ description: "File path to read",
+ }),
+ async handler(args) {
+ await bootstrap({ cwd: process.cwd() }, async () => {
+ const content = await File.read(path.resolve(args.path))
+ console.log(content)
+ })
+ },
+})
diff --git a/packages/opencode/src/cli/cmd/debug/index.ts b/packages/opencode/src/cli/cmd/debug/index.ts
new file mode 100644
index 000000000..5a765997f
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/debug/index.ts
@@ -0,0 +1,17 @@
+import { cmd } from "../cmd"
+import { FileCommand } from "./file"
+import { LSPCommand } from "./lsp"
+import { RipgrepCommand } from "./ripgrep"
+import { SnapshotCommand } from "./snapshot"
+
+export const DebugCommand = cmd({
+ command: "debug",
+ builder: (yargs) =>
+ yargs
+ .command(LSPCommand)
+ .command(RipgrepCommand)
+ .command(FileCommand)
+ .command(SnapshotCommand)
+ .demandCommand(),
+ async handler() {},
+})
diff --git a/packages/opencode/src/cli/cmd/debug/lsp.ts b/packages/opencode/src/cli/cmd/debug/lsp.ts
new file mode 100644
index 000000000..d596bf6c5
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/debug/lsp.ts
@@ -0,0 +1,37 @@
+import { LSP } from "../../../lsp"
+import { bootstrap } from "../../bootstrap"
+import { cmd } from "../cmd"
+import { Log } from "../../../util/log"
+
+export const LSPCommand = cmd({
+ command: "lsp",
+ builder: (yargs) =>
+ yargs.command(DiagnosticsCommand).command(SymbolsCommand).demandCommand(),
+ async handler() {},
+})
+
+const DiagnosticsCommand = cmd({
+ command: "diagnostics <file>",
+ builder: (yargs) =>
+ yargs.positional("file", { type: "string", demandOption: true }),
+ async handler(args) {
+ await bootstrap({ cwd: process.cwd() }, async () => {
+ await LSP.touchFile(args.file, true)
+ console.log(await LSP.diagnostics())
+ })
+ },
+})
+
+export const SymbolsCommand = cmd({
+ command: "symbols <query>",
+ builder: (yargs) =>
+ yargs.positional("query", { type: "string", demandOption: true }),
+ async handler(args) {
+ await bootstrap({ cwd: process.cwd() }, async () => {
+ await LSP.touchFile("./src/index.ts", true)
+ using _ = Log.Default.time("symbols")
+ const results = await LSP.workspaceSymbol(args.query)
+ console.log(JSON.stringify(results, null, 2))
+ })
+ },
+})
diff --git a/packages/opencode/src/cli/cmd/debug.ts b/packages/opencode/src/cli/cmd/debug/ripgrep.ts
index 6d218733b..c71368671 100644
--- a/packages/opencode/src/cli/cmd/debug.ts
+++ b/packages/opencode/src/cli/cmd/debug/ripgrep.ts
@@ -1,52 +1,9 @@
-import { App } from "../../app/app"
-import { Ripgrep } from "../../file/ripgrep"
-import { File } from "../../file"
-import { LSP } from "../../lsp"
-import { Log } from "../../util/log"
-import { bootstrap } from "../bootstrap"
-import { cmd } from "./cmd"
-import path from "path"
+import { App } from "../../../app/app"
+import { Ripgrep } from "../../../file/ripgrep"
+import { bootstrap } from "../../bootstrap"
+import { cmd } from "../cmd"
-export const DebugCommand = cmd({
- command: "debug",
- builder: (yargs) =>
- yargs
- .command(DiagnosticsCommand)
- .command(RipgrepCommand)
- .command(SymbolsCommand)
- .command(FileReadCommand)
- .demandCommand(),
- async handler() {},
-})
-
-const DiagnosticsCommand = cmd({
- command: "diagnostics <file>",
- builder: (yargs) =>
- yargs.positional("file", { type: "string", demandOption: true }),
- async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
- await LSP.touchFile(args.file, true)
- await LSP.touchFile(args.file, true)
- console.log(await LSP.diagnostics())
- })
- },
-})
-
-const SymbolsCommand = cmd({
- command: "symbols <query>",
- builder: (yargs) =>
- yargs.positional("query", { type: "string", demandOption: true }),
- async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
- await LSP.touchFile("./src/index.ts", true)
- using _ = Log.Default.time("symbols")
- const results = await LSP.workspaceSymbol(args.query)
- console.log(JSON.stringify(results, null, 2))
- })
- },
-})
-
-const RipgrepCommand = cmd({
+export const RipgrepCommand = cmd({
command: "rg",
builder: (yargs) =>
yargs
@@ -128,19 +85,3 @@ const SearchCommand = cmd({
console.log(JSON.stringify(results, null, 2))
},
})
-
-const FileReadCommand = cmd({
- command: "file-read <path>",
- builder: (yargs) =>
- yargs.positional("path", {
- type: "string",
- demandOption: true,
- description: "File path to read",
- }),
- async handler(args) {
- await bootstrap({ cwd: process.cwd() }, async () => {
- const content = await File.read(path.resolve(args.path))
- console.log(content)
- })
- },
-})
diff --git a/packages/opencode/src/cli/cmd/debug/snapshot.ts b/packages/opencode/src/cli/cmd/debug/snapshot.ts
new file mode 100644
index 000000000..a6d129d5a
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/debug/snapshot.ts
@@ -0,0 +1,39 @@
+import { Snapshot } from "../../../snapshot"
+import { bootstrap } from "../../bootstrap"
+import { cmd } from "../cmd"
+
+export const SnapshotCommand = cmd({
+ command: "snapshot",
+ builder: (yargs) =>
+ yargs
+ .command(SnapshotCreateCommand)
+ .command(SnapshotRestoreCommand)
+ .demandCommand(),
+ async handler() {},
+})
+
+export const SnapshotCreateCommand = cmd({
+ command: "create",
+ async handler() {
+ await bootstrap({ cwd: process.cwd() }, async () => {
+ const result = await Snapshot.create("test")
+ console.log(result)
+ })
+ },
+})
+
+export const SnapshotRestoreCommand = cmd({
+ command: "restore <commit>",
+ builder: (yargs) =>
+ yargs.positional("commit", {
+ type: "string",
+ description: "commit",
+ demandOption: true,
+ }),
+ async handler(args) {
+ await bootstrap({ cwd: process.cwd() }, async () => {
+ await Snapshot.restore("test", args.commit)
+ console.log("restored")
+ })
+ },
+})
diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts
new file mode 100644
index 000000000..bf8ea05f6
--- /dev/null
+++ b/packages/opencode/src/snapshot/index.ts
@@ -0,0 +1,85 @@
+import { App } from "../app/app"
+import {
+ add,
+ commit,
+ init,
+ checkout,
+ statusMatrix,
+ remove,
+} from "isomorphic-git"
+import path from "path"
+import fs from "fs"
+import { Ripgrep } from "../file/ripgrep"
+import { Log } from "../util/log"
+
+export namespace Snapshot {
+ const log = Log.create({ service: "snapshot" })
+
+ export async function create(sessionID: string) {
+ 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,
+ })
+ }
+ }
+ const result = await commit({
+ fs,
+ gitdir: git,
+ dir: app.path.cwd,
+ message: "snapshot",
+ author: {
+ name: "opencode",
+ email: "[email protected]",
+ },
+ })
+ log.info("commit", { result })
+ return result
+ }
+
+ 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,
+ })
+ }
+
+ function gitdir(sessionID: string) {
+ const app = App.info()
+ return path.join(app.path.data, "snapshot", sessionID)
+ }
+}