summaryrefslogtreecommitdiffhomepage
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
parent33b5fe236a204a3d1d935a94e1d1d5f3a6f312a8 (diff)
downloadopencode-11d042be25ee0509db323dc117724b0ac9e4610a.tar.gz
opencode-11d042be25ee0509db323dc117724b0ac9e4610a.zip
snapshot functionality
-rw-r--r--bun.lock25
-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
8 files changed, 234 insertions, 65 deletions
diff --git a/bun.lock b/bun.lock
index 1dabf9a68..5457a1408 100644
--- a/bun.lock
+++ b/bun.lock
@@ -36,6 +36,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",
@@ -541,6 +542,8 @@
"astro-expressive-code": ["[email protected]", "", { "dependencies": { "rehype-expressive-code": "^0.41.2" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" } }, "sha512-HN0jWTnhr7mIV/2e6uu4PPRNNo/k4UEgTLZqbp3MrHU+caCARveG2yZxaZVBmxyiVdYqW5Pd3u3n2zjnshixbw=="],
+ "async-lock": ["[email protected]", "", {}, "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="],
+
"atomic-sleep": ["[email protected]", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
"available-typed-arrays": ["[email protected]", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
@@ -633,6 +636,8 @@
"ci-info": ["[email protected]", "", {}, "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg=="],
+ "clean-git-ref": ["[email protected]", "", {}, "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw=="],
+
"cli-boxes": ["[email protected]", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
"cliui": ["[email protected]", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
@@ -669,6 +674,8 @@
"cors": ["[email protected]", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
+ "crc-32": ["[email protected]", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
+
"cross-fetch": ["[email protected]", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
"crossws": ["[email protected]", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
@@ -725,6 +732,8 @@
"diff-match-patch": ["[email protected]", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="],
+ "diff3": ["[email protected]", "", {}, "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g=="],
+
"direction": ["[email protected]", "", { "bin": { "direction": "cli.js" } }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="],
"dlv": ["[email protected]", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
@@ -939,6 +948,8 @@
"ieee754": ["[email protected]", "", {}, "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="],
+ "ignore": ["[email protected]", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
+
"import-meta-resolve": ["[email protected]", "", {}, "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw=="],
"inherits": ["[email protected]", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
@@ -987,6 +998,8 @@
"isarray": ["[email protected]", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
+ "isomorphic-git": ["[email protected]", "", { "dependencies": { "async-lock": "^1.4.1", "clean-git-ref": "^2.0.1", "crc-32": "^1.2.0", "diff3": "0.0.3", "ignore": "^5.1.4", "minimisted": "^2.0.0", "pako": "^1.0.10", "path-browserify": "^1.0.1", "pify": "^4.0.1", "readable-stream": "^3.4.0", "sha.js": "^2.4.9", "simple-get": "^4.0.1" }, "bin": { "isogit": "cli.cjs" } }, "sha512-NZCS7qpLkCZ1M/IrujYBD31sM6pd/fMVArK4fz4I7h6m0rUW2AsYU7S7zXeABuHL6HIfW6l53b4UQ/K441CQjg=="],
+
"jmespath": ["[email protected]", "", {}, "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="],
"jose": ["[email protected]", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
@@ -1169,6 +1182,8 @@
"minimist": ["[email protected]", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+ "minimisted": ["[email protected]", "", { "dependencies": { "minimist": "^1.2.5" } }, "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA=="],
+
"mkdirp-classic": ["[email protected]", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
"mri": ["[email protected]", "", {}, "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w=="],
@@ -1247,7 +1262,7 @@
"pagefind": ["[email protected]", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.3.0", "@pagefind/darwin-x64": "1.3.0", "@pagefind/linux-arm64": "1.3.0", "@pagefind/linux-x64": "1.3.0", "@pagefind/windows-x64": "1.3.0" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-8KPLGT5g9s+olKMRTU9LFekLizkVIu9tes90O1/aigJ0T5LmyPqTzGJrETnSw3meSYg58YH7JTzhTTW/3z6VAw=="],
- "pako": ["[email protected]", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
+ "pako": ["[email protected]", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
"parse-entities": ["[email protected]", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
@@ -1257,6 +1272,8 @@
"parseurl": ["[email protected]", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
+ "path-browserify": ["[email protected]", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
+
"path-to-regexp": ["[email protected]", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
"pathe": ["[email protected]", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -1267,6 +1284,8 @@
"picomatch": ["[email protected]", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
+ "pify": ["[email protected]", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="],
+
"pino": ["[email protected]", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.0.0", "on-exit-leak-free": "^0.2.0", "pino-abstract-transport": "v0.5.0", "pino-std-serializers": "^4.0.0", "process-warning": "^1.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.1.0", "safe-stable-stringify": "^2.1.0", "sonic-boom": "^2.2.1", "thread-stream": "^0.15.1" }, "bin": { "pino": "bin.js" } }, "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg=="],
"pino-abstract-transport": ["[email protected]", "", { "dependencies": { "duplexify": "^4.1.2", "split2": "^4.0.0" } }, "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ=="],
@@ -1417,6 +1436,8 @@
"setprototypeof": ["[email protected]", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
+ "sha.js": ["[email protected]", "", { "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" }, "bin": { "sha.js": "./bin.js" } }, "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ=="],
+
"sharp": ["[email protected]", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", "semver": "^7.5.4", "simple-get": "^4.0.1", "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" } }, "sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ=="],
"shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.4.2", "@shikijs/engine-javascript": "3.4.2", "@shikijs/engine-oniguruma": "3.4.2", "@shikijs/langs": "3.4.2", "@shikijs/themes": "3.4.2", "@shikijs/types": "3.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-wuxzZzQG8kvZndD7nustrNFIKYJ1jJoWIPaBpVe2+KHSvtzMi4SBjOxrigs8qeqce/l3U0cwiC+VAkLKSunHQQ=="],
@@ -1793,6 +1814,8 @@
"token-types/ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+ "unicode-trie/pako": ["[email protected]", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
+
"unstorage/lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"vscode-languageserver-protocol/vscode-jsonrpc": ["[email protected]", "", {}, "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw=="],
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)
+ }
+}