This commit is contained in:
Dax Raad
2025-08-31 00:03:16 -04:00
parent 46af0aca8e
commit 8905b31bdb
105 changed files with 1352 additions and 16882 deletions
-151
View File
@@ -1,151 +0,0 @@
import "zod-openapi/extend"
import { Log } from "../util/log"
import { Context } from "../util/context"
import { Filesystem } from "../util/filesystem"
import { Global } from "../global"
import path from "path"
import os from "os"
import { z } from "zod"
import { Project } from "../project/project"
import { Instance } from "../project/instance"
export namespace App {
const log = Log.create({ service: "app" })
export const Info = z
.object({
hostname: z.string(),
git: z.boolean(),
path: z.object({
home: z.string(),
config: z.string(),
data: z.string(),
root: z.string(),
cwd: z.string(),
state: z.string(),
}),
time: z.object({
initialized: z.number().optional(),
}),
})
.openapi({
ref: "App",
})
export type Info = z.infer<typeof Info>
const ctx = Context.create<{
info: Info
services: Map<any, { state: any; shutdown?: (input: any) => Promise<void> }>
}>("app")
export const use = ctx.use
const APP_JSON = "app.json"
export type Input = {
cwd: string
}
export const provideExisting = ctx.provide
export async function provide<T>(input: Input, cb: (app: App.Info) => Promise<T>) {
log.info("creating", {
cwd: input.cwd,
})
const git = await Filesystem.findUp(".git", input.cwd).then(([x]) => (x ? path.dirname(x) : undefined))
log.info("git", { git })
const data = path.join(Global.Path.data, "project", git ? directory(git) : "global")
const stateFile = Bun.file(path.join(data, APP_JSON))
const state = (await stateFile.json().catch(() => ({}))) as {
initialized: number
}
await stateFile.write(JSON.stringify(state))
const services = new Map<
any,
{
state: any
shutdown?: (input: any) => Promise<void>
}
>()
const root = git ?? input.cwd
const info: Info = {
hostname: os.hostname(),
time: {
initialized: state.initialized,
},
git: git !== undefined,
path: {
home: os.homedir(),
config: Global.Path.config,
state: Global.Path.state,
data,
root,
cwd: input.cwd,
},
}
const app = {
services,
info,
}
const projects = await Project.list()
const project = projects
.toSorted((a, b) => a.worktree.length - b.worktree.length)
.find((x) => Filesystem.contains(x.worktree, input.cwd))
if (!project) {
throw new Error("No project found")
}
return ctx.provide(app, async () => {
return Project.provide(project, async () => {
const result = await Instance.provide(
{
worktree: app.info.path.root,
directory: app.info.path.cwd,
},
async () => {
try {
const result = await cb(app.info)
return result
} finally {
for (const [key, entry] of app.services.entries()) {
if (!entry.shutdown) continue
log.info("shutdown", { name: key })
await entry.shutdown?.(await entry.state)
}
await Instance.dispose()
}
},
)
return result
})
})
}
export function info() {
return ctx.use().info
}
export async function initialize() {
const { info } = ctx.use()
info.time.initialized = Date.now()
await Bun.write(
path.join(info.path.data, APP_JSON),
JSON.stringify({
initialized: Date.now(),
}),
)
}
function directory(input: string): string {
return input
.split(path.sep)
.filter(Boolean)
.join("-")
.replace(/[^A-Za-z0-9_]/g, "-")
}
}
+4 -4
View File
@@ -1,17 +1,17 @@
import { App } from "../app/app"
import { Format } from "../format"
import { LSP } from "../lsp"
import { Plugin } from "../plugin"
import { Instance } from "../project/instance"
import { Share } from "../share/share"
import { Snapshot } from "../snapshot"
export async function bootstrap<T>(input: App.Input, cb: (app: App.Info) => Promise<T>) {
return App.provide(input, async (app) => {
export async function bootstrap<T>(directory: string, cb: () => Promise<T>) {
return Instance.provide(directory, async () => {
await Plugin.init()
Share.init()
Format.init()
LSP.init()
Snapshot.init()
return cb(app)
return cb()
})
}
+6 -5
View File
@@ -5,25 +5,26 @@ import { Global } from "../../global"
import { Agent } from "../../agent/agent"
import path from "path"
import matter from "gray-matter"
import { App } from "../../app/app"
import { Instance } from "../../project/instance"
const AgentCreateCommand = cmd({
command: "create",
describe: "create a new agent",
async handler() {
await App.provide({ cwd: process.cwd() }, async (app) => {
await Instance.provide(process.cwd(), async () => {
UI.empty()
prompts.intro("Create agent")
const project = Instance.project
let scope: "global" | "project" = "global"
if (app.git) {
if (project.vcs === "git") {
const scopeResult = await prompts.select({
message: "Location",
options: [
{
label: "Current project",
value: "project" as const,
hint: app.path.root,
hint: Instance.worktree,
},
{
label: "Global",
@@ -116,7 +117,7 @@ const AgentCreateCommand = cmd({
const content = matter.stringify(generated.systemPrompt, frontmatter)
const filePath = path.join(
scope === "global" ? Global.Path.config : path.join(app.path.root, ".opencode"),
scope === "global" ? Global.Path.config : path.join(Instance.worktree, ".opencode"),
`agent`,
`${generated.identifier}.md`,
)
+2 -2
View File
@@ -8,7 +8,7 @@ import path from "path"
import os from "os"
import { Global } from "../../global"
import { Plugin } from "../../plugin"
import { App } from "../../app/app"
import { Instance } from "../../project/instance"
export const AuthCommand = cmd({
command: "auth",
@@ -74,7 +74,7 @@ export const AuthLoginCommand = cmd({
type: "string",
}),
async handler(args) {
await App.provide({ cwd: process.cwd() }, async () => {
await Instance.provide(process.cwd(), async () => {
UI.empty()
prompts.intro("Add credential")
if (args.url) {
@@ -1,20 +0,0 @@
import { App } from "../../../app/app"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
const AppInfoCommand = cmd({
command: "info",
builder: (yargs) => yargs,
async handler() {
await bootstrap({ cwd: process.cwd() }, async () => {
const app = App.info()
console.log(JSON.stringify(app, null, 2))
})
},
})
export const AppCommand = cmd({
command: "app",
builder: (yargs) => yargs.command(AppInfoCommand).demandCommand(),
async handler() {},
})
+3 -3
View File
@@ -11,7 +11,7 @@ const FileReadCommand = cmd({
description: "File path to read",
}),
async handler(args) {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
const content = await File.read(args.path)
console.log(content)
})
@@ -22,7 +22,7 @@ const FileStatusCommand = cmd({
command: "status",
builder: (yargs) => yargs,
async handler() {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
const status = await File.status()
console.log(JSON.stringify(status, null, 2))
})
@@ -38,7 +38,7 @@ const FileListCommand = cmd({
description: "File path to list",
}),
async handler(args) {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
const files = await File.list(args.path)
console.log(JSON.stringify(files, null, 2))
})
+1 -3
View File
@@ -1,7 +1,6 @@
import { Global } from "../../../global"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
import { AppCommand } from "./app"
import { FileCommand } from "./file"
import { LSPCommand } from "./lsp"
import { RipgrepCommand } from "./ripgrep"
@@ -12,7 +11,6 @@ export const DebugCommand = cmd({
command: "debug",
builder: (yargs) =>
yargs
.command(AppCommand)
.command(LSPCommand)
.command(RipgrepCommand)
.command(FileCommand)
@@ -22,7 +20,7 @@ export const DebugCommand = cmd({
.command({
command: "wait",
async handler() {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
await new Promise((resolve) => setTimeout(resolve, 1_000 * 60 * 60 * 24))
})
},
+3 -3
View File
@@ -14,7 +14,7 @@ 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 bootstrap(process.cwd(), async () => {
await LSP.touchFile(args.file, true)
console.log(JSON.stringify(await LSP.diagnostics(), null, 2))
})
@@ -25,7 +25,7 @@ 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 bootstrap(process.cwd(), async () => {
using _ = Log.Default.time("symbols")
const results = await LSP.workspaceSymbol(args.query)
console.log(JSON.stringify(results, null, 2))
@@ -37,7 +37,7 @@ export const DocumentSymbolsCommand = cmd({
command: "document-symbols <uri>",
builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }),
async handler(args) {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
using _ = Log.Default.time("document-symbols")
const results = await LSP.documentSymbol(args.uri)
console.log(JSON.stringify(results, null, 2))
@@ -16,7 +16,7 @@ const TreeCommand = cmd({
type: "number",
}),
async handler(args) {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
console.log(await Ripgrep.tree({ cwd: Instance.directory, limit: args.limit }))
})
},
@@ -39,7 +39,7 @@ const FilesCommand = cmd({
description: "Limit number of results",
}),
async handler(args) {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
const files = await Ripgrep.files({
cwd: Instance.directory,
query: args.query,
@@ -11,7 +11,7 @@ export const SnapshotCommand = cmd({
const TrackCommand = cmd({
command: "track",
async handler() {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
console.log(await Snapshot.track())
})
},
@@ -26,7 +26,7 @@ const PatchCommand = cmd({
demandOption: true,
}),
async handler(args) {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
console.log(await Snapshot.patch(args.hash))
})
},
@@ -41,7 +41,7 @@ const DiffCommand = cmd({
demandOption: true,
}),
async handler(args) {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
console.log(await Snapshot.diff(args.hash))
})
},
+1 -1
View File
@@ -15,7 +15,7 @@ export const ExportCommand = cmd({
})
},
handler: async (args) => {
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
let sessionID = args.sessionID
if (!sessionID) {
+4 -6
View File
@@ -6,8 +6,7 @@ import { map, pipe, sortBy, values } from "remeda"
import { UI } from "../ui"
import { cmd } from "./cmd"
import { ModelsDev } from "../../provider/models"
import { App } from "../../app/app"
import { Project } from "../../project/project"
import { Instance } from "../../project/instance"
const WORKFLOW_FILE = ".github/workflows/opencode.yml"
@@ -22,7 +21,7 @@ export const GithubInstallCommand = cmd({
command: "install",
describe: "install the GitHub agent",
async handler() {
await App.provide({ cwd: process.cwd() }, async () => {
await Instance.provide(process.cwd(), async () => {
UI.empty()
prompts.intro("Install GitHub agent")
const app = await getAppInfo()
@@ -64,8 +63,7 @@ export const GithubInstallCommand = cmd({
}
async function getAppInfo() {
const app = App.info()
const project = Project.use()
const project = Instance.project
if (project.vcs !== "git") {
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
throw new UI.CancelledError()
@@ -90,7 +88,7 @@ export const GithubInstallCommand = cmd({
throw new UI.CancelledError()
}
const [, owner, repo] = parsed
return { owner, repo, root: app.path.root }
return { owner, repo, root: Instance.worktree }
}
async function promptProvider() {
+2 -2
View File
@@ -1,4 +1,4 @@
import { App } from "../../app/app"
import { Instance } from "../../project/instance"
import { Provider } from "../../provider/provider"
import { cmd } from "./cmd"
@@ -6,7 +6,7 @@ export const ModelsCommand = cmd({
command: "models",
describe: "list all available models",
handler: async () => {
await App.provide({ cwd: process.cwd() }, async () => {
await Instance.provide(process.cwd(), async () => {
const providers = await Provider.list()
for (const [providerID, provider] of Object.entries(providers)) {
+1 -1
View File
@@ -69,7 +69,7 @@ export const RunCommand = cmd({
return
}
await bootstrap({ cwd: process.cwd() }, async () => {
await bootstrap(process.cwd(), async () => {
const session = await (async () => {
if (args.continue) {
const it = Session.list()
+8 -22
View File
@@ -1,6 +1,4 @@
import { Provider } from "../../provider/provider"
import { Server } from "../../server/server"
import { bootstrap } from "../bootstrap"
import { cmd } from "./cmd"
export const ServeCommand = cmd({
@@ -21,26 +19,14 @@ export const ServeCommand = cmd({
}),
describe: "starts a headless opencode server",
handler: async (args) => {
const cwd = process.cwd()
await bootstrap({ cwd }, async () => {
const providers = await Provider.list()
if (Object.keys(providers).length === 0) {
return "needs_provider"
}
const hostname = args.hostname
const port = args.port
const server = Server.listen({
port,
hostname,
})
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
await new Promise(() => {})
server.stop()
const hostname = args.hostname
const port = args.port
const server = Server.listen({
port,
hostname,
})
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
await new Promise(() => {})
server.stop()
},
})
+1 -2
View File
@@ -79,7 +79,7 @@ export const TuiCommand = cmd({
UI.error("Failed to change directory to " + cwd)
return
}
const result = await bootstrap({ cwd }, async (app) => {
const result = await bootstrap(cwd, async () => {
const sessionID = await (async () => {
if (args.continue) {
const it = Session.list()
@@ -146,7 +146,6 @@ export const TuiCommand = cmd({
...process.env,
CGO_ENABLED: "0",
OPENCODE_SERVER: server.url.toString(),
OPENCODE_APP_INFO: JSON.stringify(app),
},
onExit: () => {
server.stop()
+8 -9
View File
@@ -3,7 +3,6 @@ import { Bus } from "../bus"
import { $ } from "bun"
import { createPatch } from "diff"
import path from "path"
import { App } from "../app/app"
import fs from "fs"
import ignore from "ignore"
import { Log } from "../util/log"
@@ -48,7 +47,7 @@ export namespace File {
}
export async function status() {
const project = Project.use()
const project = Instance.project
if (project.vcs !== "git") return []
const diffOutput = await $`git diff --numstat HEAD`.cwd(Instance.directory).quiet().nothrow().text()
@@ -119,7 +118,7 @@ export namespace File {
export async function read(file: string) {
using _ = log.time("read", { file })
const project = Project.use()
const project = Instance.project
const full = path.join(Instance.directory, file)
const content = await Bun.file(full)
.text()
@@ -141,22 +140,22 @@ export namespace File {
export async function list(dir?: string) {
const exclude = [".git", ".DS_Store"]
const app = App.info()
const project = Instance.project
let ignored = (_: string) => false
if (app.git) {
const gitignore = Bun.file(path.join(app.path.root, ".gitignore"))
if (project.vcs === "git") {
const gitignore = Bun.file(path.join(Instance.worktree, ".gitignore"))
if (await gitignore.exists()) {
const ig = ignore().add(await gitignore.text())
ignored = ig.ignores.bind(ig)
}
}
const resolved = dir ? path.join(app.path.cwd, dir) : app.path.cwd
const resolved = dir ? path.join(Instance.directory, dir) : Instance.directory
const nodes: Node[] = []
for (const entry of await fs.promises.readdir(resolved, { withFileTypes: true })) {
if (exclude.includes(entry.name)) continue
const fullPath = path.join(resolved, entry.name)
const relativePath = path.relative(app.path.cwd, fullPath)
const relativeToRoot = path.relative(app.path.root, fullPath)
const relativePath = path.relative(Instance.directory, fullPath)
const relativeToRoot = path.relative(Instance.worktree, fullPath)
const type = entry.isDirectory() ? "directory" : "file"
nodes.push({
name: entry.name,
+5 -11
View File
@@ -1,7 +1,6 @@
import { z } from "zod"
import { Bus } from "../bus"
import fs from "fs"
import { App } from "../app/app"
import { Log } from "../util/log"
import { Flag } from "../flag/flag"
import { Instance } from "../project/instance"
@@ -20,19 +19,14 @@ export namespace FileWatcher {
}
const state = Instance.state(
() => {
const app = App.use()
if (!app.info.git) return {}
if (Instance.project.vcs !== "git") return {}
try {
const watcher = fs.watch(app.info.path.cwd, { recursive: true }, (event, file) => {
const watcher = fs.watch(Instance.directory, { recursive: true }, (event, file) => {
log.info("change", { file, event })
if (!file) return
// for some reason async local storage is lost here
// https://github.com/oven-sh/bun/issues/20754
App.provideExisting(app, async () => {
Bus.publish(Event.Updated, {
file,
event,
})
Bus.publish(Event.Updated, {
file,
event,
})
})
return { watcher }
+1
View File
@@ -107,6 +107,7 @@ try {
name: e.name,
message: e.message,
cause: e.cause?.toString(),
stack: e.stack,
})
}
+4 -3
View File
@@ -7,7 +7,6 @@ import { Server } from "../server/server"
import { BunProc } from "../bun"
import { Instance } from "../project/instance"
import { Flag } from "../flag/flag"
import { App } from "../app/app"
export namespace Plugin {
const log = Log.create({ service: "plugin" })
@@ -15,13 +14,15 @@ export namespace Plugin {
const state = Instance.state(async () => {
const client = createOpencodeClient({
baseUrl: "http://localhost:4096",
fetch: async (...args) => Server.app().fetch(...args),
fetch: async (...args) => Server.App.fetch(...args),
})
const config = await Config.get()
const hooks = []
const input = {
client,
app: App.info(),
project: Instance.project,
worktree: Instance.worktree,
directory: Instance.directory,
$: Bun.$,
}
const plugins = [...(config.plugin ?? [])]
+9 -2
View File
@@ -1,16 +1,23 @@
import { Context } from "../util/context"
import { Project } from "./project"
import { State } from "./state"
const context = Context.create<{ directory: string; worktree: string }>("path")
const context = Context.create<{ directory: string; worktree: string; project: Project.Info }>("path")
export const Instance = {
provide: context.provide,
async provide<R>(directory: string, cb: () => R): Promise<R> {
const project = await Project.fromDirectory(directory)
return context.provide({ directory, worktree: project.worktree, project }, cb)
},
get directory() {
return context.use().directory
},
get worktree() {
return context.use().worktree
},
get project() {
return context.use().project
},
state<S>(init: () => S, dispose?: (state: Awaited<S>) => Promise<void>): () => S {
return State.create(() => Instance.directory, init, dispose)
},
+70 -61
View File
@@ -1,83 +1,92 @@
import z from "zod"
import { lazy } from "../util/lazy"
import { Filesystem } from "../util/filesystem"
import path from "path"
import { $ } from "bun"
import { StorageNext } from "../storage/storage-next"
import { Context } from "../util/context"
import { Log } from "../util/log"
export namespace Project {
export const Info = z.object({
id: z.string(),
worktree: z.string(),
vcs: z.literal("git").optional(),
time: z.object({
created: z.number(),
initialized: z.number().optional(),
}),
})
const log = Log.create({ service: "project" })
export const Info = z
.object({
id: z.string(),
worktree: z.string(),
vcs: z.literal("git").optional(),
time: z.object({
created: z.number(),
initialized: z.number().optional(),
}),
})
.openapi({
ref: "Project",
})
export type Info = z.infer<typeof Info>
const context = Context.create<Info>("project")
export const use = context.use
export const provide = context.provide
const init = lazy(async () => {
const cwd = process.cwd()
const matches = Filesystem.up({ targets: [".git"], start: cwd })
const git = await matches.next().then((x) => x.value)
await matches.return()
if (!git) {
await StorageNext.write<Info>(["project", "global"], {
id: "global",
worktree: "/",
time: {
created: Date.now(),
},
})
return
}
let worktree = path.dirname(git)
const [id] = await $`git rev-list --max-parents=0 --all`
.quiet()
.nothrow()
.cwd(worktree)
.text()
.then((x) =>
x
.split("\n")
.filter(Boolean)
.map((x) => x.trim())
.toSorted(),
)
worktree = path.dirname(
await $`git rev-parse --path-format=absolute --git-common-dir`
const cache = new Map<string, Info>()
export async function fromDirectory(directory: string) {
log.info("fromDirectory", { directory })
const fn = async () => {
const matches = Filesystem.up({ targets: [".git"], start: directory })
const git = await matches.next().then((x) => x.value)
await matches.return()
if (!git) {
const project: Info = {
id: "global",
worktree: "/",
time: {
created: Date.now(),
},
}
await StorageNext.write<Info>(["project", "global"], project)
return project
}
let worktree = path.dirname(git)
const [id] = await $`git rev-list --max-parents=0 --all`
.quiet()
.nothrow()
.cwd(worktree)
.text()
.then((x) => x.trim()),
)
await StorageNext.write<Info>(["project", id], {
id,
worktree,
vcs: "git",
time: {
created: Date.now(),
},
})
})
.then((x) =>
x
.split("\n")
.filter(Boolean)
.map((x) => x.trim())
.toSorted(),
)
worktree = path.dirname(
await $`git rev-parse --path-format=absolute --git-common-dir`
.quiet()
.nothrow()
.cwd(worktree)
.text()
.then((x) => x.trim()),
)
const project: Info = {
id,
worktree,
vcs: "git",
time: {
created: Date.now(),
},
}
await StorageNext.write<Info>(["project", id], project)
return project
}
if (cache.has(directory)) {
return cache.get(directory)!
}
const result = await fn()
cache.set(directory, result)
return result
}
export async function setInitialized() {
const project = use()
await StorageNext.update<Info>(["project", project.id], (draft) => {
export async function setInitialized(projectID: string) {
await StorageNext.update<Info>(["project", projectID], (draft) => {
draft.time.initialized = Date.now()
})
}
export async function list() {
await init()
const keys = await StorageNext.list(["project"])
return await Promise.all(keys.map((x) => StorageNext.read<Info>(x)))
}
+26
View File
@@ -0,0 +1,26 @@
import { Hono } from "hono"
import { describeRoute } from "hono-openapi"
import { resolver, validator as zValidator } from "hono-openapi/zod"
import { Project } from "../project/project"
export const ProjectRoute = new Hono().get(
"/",
describeRoute({
description: "List all projects",
operationId: "project.list",
responses: {
200: {
description: "List of projects",
content: {
"application/json": {
schema: resolver(Project.Info.array()),
},
},
},
},
}),
async (c) => {
const projects = await Project.list()
return c.json(projects)
},
)
File diff suppressed because it is too large Load Diff
+8 -14
View File
@@ -51,7 +51,6 @@ import { ulid } from "ulid"
import { defer } from "../util/defer"
import { Command } from "../command"
import { $ } from "bun"
import { App } from "../app/app"
export namespace Session {
const log = Log.create({ service: "session" })
@@ -175,11 +174,10 @@ export namespace Session {
}
export async function createNext(input: { id?: string; title?: string; parentID?: string; directory: string }) {
const project = Project.use()
const result: Info = {
id: Identifier.descending("session", input.id),
version: Installation.VERSION,
projectID: project.id,
projectID: Instance.project.id,
directory: input.directory,
parentID: input.parentID,
title: input.title ?? createDefaultTitle(!!input.parentID),
@@ -189,7 +187,7 @@ export namespace Session {
},
}
log.info("created", result)
await StorageNext.write(["session", project.id, result.id], result)
await StorageNext.write(["session", Instance.project.id, result.id], result)
const cfg = await Config.get()
if (!result.parentID && (Flag.OPENCODE_AUTO_SHARE || cfg.share === "auto"))
share(result.id)
@@ -208,8 +206,7 @@ export namespace Session {
}
export async function get(id: string) {
const project = Project.use()
const read = await StorageNext.read<Info>(["session", project.id, id])
const read = await StorageNext.read<Info>(["session", Instance.project.id, id])
return read as Info
}
@@ -253,7 +250,7 @@ export namespace Session {
}
export async function update(id: string, editor: (session: Info) => void) {
const project = Project.use()
const project = Instance.project
const result = await StorageNext.update<Info>(["session", project.id, id], (draft) => {
editor(draft)
draft.time.updated = Date.now()
@@ -298,14 +295,14 @@ export namespace Session {
}
export async function* list() {
const project = Project.use()
const project = Instance.project
for (const item of await StorageNext.list(["session", project.id])) {
yield StorageNext.read<Info>(item)
}
}
export async function children(parentID: string) {
const project = Project.use()
const project = Instance.project
const result = [] as Session.Info[]
for (const item of await StorageNext.list(["session", project.id])) {
const session = await StorageNext.read<Info>(item)
@@ -327,7 +324,7 @@ export namespace Session {
}
export async function remove(sessionID: string, emitEvent = true) {
const project = Project.use()
const project = Instance.project
try {
abort(sessionID)
const session = await get(sessionID)
@@ -1125,7 +1122,6 @@ export namespace Session {
},
}
await updatePart(part)
const app = App.info()
const shell = process.env["SHELL"] ?? "bash"
const shellName = path.basename(shell)
@@ -1145,7 +1141,7 @@ export namespace Session {
const args = isFishOrNu ? ["-c", script] : ["-c", "-l", script]
const proc = spawn(shell, args, {
cwd: app.path.cwd,
cwd: Instance.directory,
signal: abort.signal,
stdio: ["ignore", "pipe", "pipe"],
env: {
@@ -1257,8 +1253,6 @@ export namespace Session {
},
] as ChatInput["parts"]
const app = App.info()
for (const match of fileMatches) {
const filename = match[1]
const filepath = filename.startsWith("~/")
+1 -1
View File
@@ -31,7 +31,7 @@ export namespace SystemPrompt {
}
export async function environment() {
const project = Project.use()
const project = Instance.project
return [
[
`Here is some useful information about the environment you are running in:`,
+1 -5
View File
@@ -45,11 +45,7 @@ export namespace Share {
})
}
export function init() {
Bus.subscribe(StorageNext.Event.Write, async (payload) => {
await sync(payload.properties.key.join("/"), payload.properties.content)
})
}
export function init() {}
export const URL =
process.env["OPENCODE_API"] ??
+2 -4
View File
@@ -5,7 +5,6 @@ import { Log } from "../util/log"
import { Global } from "../global"
import { z } from "zod"
import { Config } from "../config/config"
import { Project } from "../project/project"
import { Instance } from "../project/instance"
export namespace Snapshot {
@@ -26,8 +25,7 @@ export namespace Snapshot {
}
export async function track() {
const project = Project.use()
if (project.vcs !== "git") return
if (Instance.project.vcs !== "git") return
const cfg = await Config.get()
if (cfg.snapshot === false) return
const git = gitdir()
@@ -104,7 +102,7 @@ export namespace Snapshot {
}
function gitdir() {
const project = Project.use()
const project = Instance.project
return path.join(Global.Path.data, "snapshot", project.id)
}
}
@@ -4,14 +4,8 @@ import fs from "fs/promises"
import { Global } from "../global"
import { lazy } from "../util/lazy"
import { Lock } from "../util/lock"
import { Bus } from "../bus"
import z from "zod"
export namespace StorageNext {
export const Event = {
Write: Bus.event("storage.write", z.object({ key: z.string().array(), content: z.any() })),
}
const log = Log.create({ service: "storage" })
type Migration = (dir: string) => Promise<void>
@@ -55,7 +49,6 @@ export namespace StorageNext {
const content = await Bun.file(target).json()
fn(content)
await Bun.write(target, JSON.stringify(content, null, 2))
Bus.publish(Event.Write, { key, content })
return content as T
}
@@ -64,7 +57,6 @@ export namespace StorageNext {
const target = path.join(dir, ...key) + ".json"
using _ = await Lock.write("storage")
await Bun.write(target, JSON.stringify(content, null, 2))
Bus.publish(Event.Write, { key, content })
}
const glob = new Bun.Glob("**/*")
+3 -3
View File
@@ -1,8 +1,8 @@
import { describe, expect, test } from "bun:test"
import { App } from "../../src/app/app"
import path from "path"
import { BashTool } from "../../src/tool/bash"
import { Log } from "../../src/util/log"
import { Instance } from "../../src/project/instance"
const ctx = {
sessionID: "test",
@@ -19,7 +19,7 @@ Log.init({ print: false })
describe("tool.bash", () => {
test("basic", async () => {
await App.provide({ cwd: projectRoot }, async () => {
await Instance.provide(projectRoot, async () => {
const result = await bash.execute(
{
command: "echo 'test'",
@@ -33,7 +33,7 @@ describe("tool.bash", () => {
})
test("cd ../ should fail outside of project root", async () => {
await App.provide({ cwd: projectRoot }, async () => {
await Instance.provide(projectRoot, async () => {
expect(
bash.execute(
{
+4 -4
View File
@@ -1,8 +1,8 @@
import { describe, expect, test } from "bun:test"
import { App } from "../../src/app/app"
import { GlobTool } from "../../src/tool/glob"
import { ListTool } from "../../src/tool/ls"
import path from "path"
import { Instance } from "../../src/project/instance"
const ctx = {
sessionID: "test",
@@ -20,7 +20,7 @@ const fixturePath = path.join(__dirname, "../fixtures/example")
describe("tool.glob", () => {
test("truncate", async () => {
await App.provide({ cwd: projectRoot }, async () => {
await Instance.provide(projectRoot, async () => {
let result = await glob.execute(
{
pattern: "**/*",
@@ -32,7 +32,7 @@ describe("tool.glob", () => {
})
})
test("basic", async () => {
await App.provide({ cwd: projectRoot }, async () => {
await Instance.provide(projectRoot, async () => {
let result = await glob.execute(
{
pattern: "*.json",
@@ -50,7 +50,7 @@ describe("tool.glob", () => {
describe("tool.ls", () => {
test("basic", async () => {
const result = await App.provide({ cwd: projectRoot }, async () => {
const result = await Instance.provide(projectRoot, async () => {
return await list.execute({ path: fixturePath, ignore: [".git"] }, ctx)
})