sync
This commit is contained in:
parent
d45d5d664c
commit
1fb54efdd2
25 changed files with 149 additions and 142 deletions
|
|
@ -12,7 +12,6 @@ export async function bootstrap<T>(input: App.Input, cb: (app: App.Info) => Prom
|
||||||
Plugin.init()
|
Plugin.init()
|
||||||
LSP.init()
|
LSP.init()
|
||||||
Snapshot.init()
|
Snapshot.init()
|
||||||
|
|
||||||
return cb(app)
|
return cb(app)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { App } from "../../../app/app"
|
|
||||||
import { Ripgrep } from "../../../file/ripgrep"
|
import { Ripgrep } from "../../../file/ripgrep"
|
||||||
|
import { Paths } from "../../../project/path"
|
||||||
import { bootstrap } from "../../bootstrap"
|
import { bootstrap } from "../../bootstrap"
|
||||||
import { cmd } from "../cmd"
|
import { cmd } from "../cmd"
|
||||||
|
|
||||||
|
|
@ -17,8 +17,7 @@ const TreeCommand = cmd({
|
||||||
}),
|
}),
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||||
const app = App.info()
|
console.log(await Ripgrep.tree({ cwd: Paths.directory, limit: args.limit }))
|
||||||
console.log(await Ripgrep.tree({ cwd: app.path.cwd, limit: args.limit }))
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -41,9 +40,8 @@ const FilesCommand = cmd({
|
||||||
}),
|
}),
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||||
const app = App.info()
|
|
||||||
const files = await Ripgrep.files({
|
const files = await Ripgrep.files({
|
||||||
cwd: app.path.cwd,
|
cwd: Paths.directory,
|
||||||
query: args.query,
|
query: args.query,
|
||||||
glob: args.glob ? [args.glob] : undefined,
|
glob: args.glob ? [args.glob] : undefined,
|
||||||
limit: args.limit,
|
limit: args.limit,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ import { Identifier } from "../../id/id"
|
||||||
import { Provider } from "../../provider/provider"
|
import { Provider } from "../../provider/provider"
|
||||||
import { Bus } from "../../bus"
|
import { Bus } from "../../bus"
|
||||||
import { MessageV2 } from "../../session/message-v2"
|
import { MessageV2 } from "../../session/message-v2"
|
||||||
|
import { Project } from "../../project/project"
|
||||||
|
import { Paths } from "../../project/path"
|
||||||
|
|
||||||
type GitHubAuthor = {
|
type GitHubAuthor = {
|
||||||
login: string
|
login: string
|
||||||
|
|
@ -178,8 +180,8 @@ export const GithubInstallCommand = cmd({
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAppInfo() {
|
async function getAppInfo() {
|
||||||
const app = App.info()
|
const project = Project.use()
|
||||||
if (!app.git) {
|
if (project.vcs !== "git") {
|
||||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||||
throw new UI.CancelledError()
|
throw new UI.CancelledError()
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +197,7 @@ export const GithubInstallCommand = cmd({
|
||||||
throw new UI.CancelledError()
|
throw new UI.CancelledError()
|
||||||
}
|
}
|
||||||
const [owner, repo] = parsed[1].split("/")
|
const [owner, repo] = parsed[1].split("/")
|
||||||
return { owner, repo, root: app.path.root }
|
return { owner, repo, root: Paths.worktree }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promptProvider() {
|
async function promptProvider() {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ import { $ } from "bun"
|
||||||
import { createPatch } from "diff"
|
import { createPatch } from "diff"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import * as git from "isomorphic-git"
|
import * as git from "isomorphic-git"
|
||||||
import { App } from "../app/app"
|
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
import { Project } from "../project/project"
|
||||||
|
|
||||||
export namespace File {
|
export namespace File {
|
||||||
const log = Log.create({ service: "file" })
|
const log = Log.create({ service: "file" })
|
||||||
|
|
@ -34,10 +35,10 @@ export namespace File {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function status() {
|
export async function status() {
|
||||||
const app = App.info()
|
const project = Project.use()
|
||||||
if (!app.git) return []
|
if (project.vcs !== "git") return []
|
||||||
|
|
||||||
const diffOutput = await $`git diff --numstat HEAD`.cwd(app.path.cwd).quiet().nothrow().text()
|
const diffOutput = await $`git diff --numstat HEAD`.cwd(Paths.directory).quiet().nothrow().text()
|
||||||
|
|
||||||
const changedFiles: Info[] = []
|
const changedFiles: Info[] = []
|
||||||
|
|
||||||
|
|
@ -54,13 +55,17 @@ export namespace File {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const untrackedOutput = await $`git ls-files --others --exclude-standard`.cwd(app.path.cwd).quiet().nothrow().text()
|
const untrackedOutput = await $`git ls-files --others --exclude-standard`
|
||||||
|
.cwd(Paths.directory)
|
||||||
|
.quiet()
|
||||||
|
.nothrow()
|
||||||
|
.text()
|
||||||
|
|
||||||
if (untrackedOutput.trim()) {
|
if (untrackedOutput.trim()) {
|
||||||
const untrackedFiles = untrackedOutput.trim().split("\n")
|
const untrackedFiles = untrackedOutput.trim().split("\n")
|
||||||
for (const filepath of untrackedFiles) {
|
for (const filepath of untrackedFiles) {
|
||||||
try {
|
try {
|
||||||
const content = await Bun.file(path.join(app.path.root, filepath)).text()
|
const content = await Bun.file(path.join(Paths.worktree, filepath)).text()
|
||||||
const lines = content.split("\n").length
|
const lines = content.split("\n").length
|
||||||
changedFiles.push({
|
changedFiles.push({
|
||||||
path: filepath,
|
path: filepath,
|
||||||
|
|
@ -75,7 +80,11 @@ export namespace File {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get deleted files
|
// Get deleted files
|
||||||
const deletedOutput = await $`git diff --name-only --diff-filter=D HEAD`.cwd(app.path.cwd).quiet().nothrow().text()
|
const deletedOutput = await $`git diff --name-only --diff-filter=D HEAD`
|
||||||
|
.cwd(Paths.directory)
|
||||||
|
.quiet()
|
||||||
|
.nothrow()
|
||||||
|
.text()
|
||||||
|
|
||||||
if (deletedOutput.trim()) {
|
if (deletedOutput.trim()) {
|
||||||
const deletedFiles = deletedOutput.trim().split("\n")
|
const deletedFiles = deletedOutput.trim().split("\n")
|
||||||
|
|
@ -91,27 +100,27 @@ export namespace File {
|
||||||
|
|
||||||
return changedFiles.map((x) => ({
|
return changedFiles.map((x) => ({
|
||||||
...x,
|
...x,
|
||||||
path: path.relative(app.path.cwd, path.join(app.path.root, x.path)),
|
path: path.relative(Paths.directory, path.join(Paths.worktree, x.path)),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function read(file: string) {
|
export async function read(file: string) {
|
||||||
using _ = log.time("read", { file })
|
using _ = log.time("read", { file })
|
||||||
const app = App.info()
|
const project = Project.use()
|
||||||
const full = path.join(app.path.cwd, file)
|
const full = path.join(Paths.directory, file)
|
||||||
const content = await Bun.file(full)
|
const content = await Bun.file(full)
|
||||||
.text()
|
.text()
|
||||||
.catch(() => "")
|
.catch(() => "")
|
||||||
.then((x) => x.trim())
|
.then((x) => x.trim())
|
||||||
if (app.git) {
|
if (project.vcs === "git") {
|
||||||
const rel = path.relative(app.path.root, full)
|
const rel = path.relative(Paths.worktree, full)
|
||||||
const diff = await git.status({
|
const diff = await git.status({
|
||||||
fs,
|
fs,
|
||||||
dir: app.path.root,
|
dir: Paths.worktree,
|
||||||
filepath: rel,
|
filepath: rel,
|
||||||
})
|
})
|
||||||
if (diff !== "unmodified") {
|
if (diff !== "unmodified") {
|
||||||
const original = await $`git show HEAD:${rel}`.cwd(app.path.root).quiet().nothrow().text()
|
const original = await $`git show HEAD:${rel}`.cwd(Paths.worktree).quiet().nothrow().text()
|
||||||
const patch = createPatch(file, original, content, "old", "new", {
|
const patch = createPatch(file, original, content, "old", "new", {
|
||||||
context: Infinity,
|
context: Infinity,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { App } from "../app/app"
|
|
||||||
import { BunProc } from "../bun"
|
import { BunProc } from "../bun"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
|
|
||||||
export interface Info {
|
export interface Info {
|
||||||
|
|
@ -63,8 +63,7 @@ export const prettier: Info = {
|
||||||
".gql",
|
".gql",
|
||||||
],
|
],
|
||||||
async enabled() {
|
async enabled() {
|
||||||
const app = App.info()
|
const items = await Filesystem.findUp("package.json", Paths.directory, Paths.worktree)
|
||||||
const items = await Filesystem.findUp("package.json", app.path.cwd, app.path.root)
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const json = await Bun.file(item).json()
|
const json = await Bun.file(item).json()
|
||||||
if (json.dependencies?.prettier) return true
|
if (json.dependencies?.prettier) return true
|
||||||
|
|
@ -109,8 +108,7 @@ export const biome: Info = {
|
||||||
".gql",
|
".gql",
|
||||||
],
|
],
|
||||||
async enabled() {
|
async enabled() {
|
||||||
const app = App.info()
|
const items = await Filesystem.findUp("biome.json", Paths.directory, Paths.worktree)
|
||||||
const items = await Filesystem.findUp("biome.json", app.path.cwd, app.path.root)
|
|
||||||
return items.length > 0
|
return items.length > 0
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -129,8 +127,7 @@ export const clang: Info = {
|
||||||
command: ["clang-format", "-i", "$FILE"],
|
command: ["clang-format", "-i", "$FILE"],
|
||||||
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
|
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
|
||||||
async enabled() {
|
async enabled() {
|
||||||
const app = App.info()
|
const items = await Filesystem.findUp(".clang-format", Paths.directory, Paths.worktree)
|
||||||
const items = await Filesystem.findUp(".clang-format", app.path.cwd, app.path.root)
|
|
||||||
return items.length > 0
|
return items.length > 0
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -150,10 +147,9 @@ export const ruff: Info = {
|
||||||
extensions: [".py", ".pyi"],
|
extensions: [".py", ".pyi"],
|
||||||
async enabled() {
|
async enabled() {
|
||||||
if (!Bun.which("ruff")) return false
|
if (!Bun.which("ruff")) return false
|
||||||
const app = App.info()
|
|
||||||
const configs = ["pyproject.toml", "ruff.toml", ".ruff.toml"]
|
const configs = ["pyproject.toml", "ruff.toml", ".ruff.toml"]
|
||||||
for (const config of configs) {
|
for (const config of configs) {
|
||||||
const found = await Filesystem.findUp(config, app.path.cwd, app.path.root)
|
const found = await Filesystem.findUp(config, Paths.directory, Paths.worktree)
|
||||||
if (found.length > 0) {
|
if (found.length > 0) {
|
||||||
if (config === "pyproject.toml") {
|
if (config === "pyproject.toml") {
|
||||||
const content = await Bun.file(found[0]).text()
|
const content = await Bun.file(found[0]).text()
|
||||||
|
|
@ -165,7 +161,7 @@ export const ruff: Info = {
|
||||||
}
|
}
|
||||||
const deps = ["requirements.txt", "pyproject.toml", "Pipfile"]
|
const deps = ["requirements.txt", "pyproject.toml", "Pipfile"]
|
||||||
for (const dep of deps) {
|
for (const dep of deps) {
|
||||||
const found = await Filesystem.findUp(dep, app.path.cwd, app.path.root)
|
const found = await Filesystem.findUp(dep, Paths.directory, Paths.worktree)
|
||||||
if (found.length > 0) {
|
if (found.length > 0) {
|
||||||
const content = await Bun.file(found[0]).text()
|
const content = await Bun.file(found[0]).text()
|
||||||
if (content.includes("ruff")) return true
|
if (content.includes("ruff")) return true
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { App } from "../app/app"
|
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import { File } from "../file"
|
import { File } from "../file"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
|
|
@ -75,7 +74,7 @@ export namespace Format {
|
||||||
log.info("running", { command: item.command })
|
log.info("running", { command: item.command })
|
||||||
const proc = Bun.spawn({
|
const proc = Bun.spawn({
|
||||||
cmd: item.command.map((x) => x.replace("$FILE", file)),
|
cmd: item.command.map((x) => x.replace("$FILE", file)),
|
||||||
cwd: App.info().path.cwd,
|
cwd: Paths.directory,
|
||||||
env: item.environment,
|
env: item.environment,
|
||||||
stdout: "ignore",
|
stdout: "ignore",
|
||||||
stderr: "ignore",
|
stderr: "ignore",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
|
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
|
||||||
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
|
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
|
||||||
import { App } from "../app/app"
|
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { LANGUAGE_EXTENSIONS } from "./language"
|
import { LANGUAGE_EXTENSIONS } from "./language"
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
|
|
@ -9,6 +8,7 @@ import z from "zod"
|
||||||
import type { LSPServer } from "./server"
|
import type { LSPServer } from "./server"
|
||||||
import { NamedError } from "../util/error"
|
import { NamedError } from "../util/error"
|
||||||
import { withTimeout } from "../util/timeout"
|
import { withTimeout } from "../util/timeout"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export namespace LSPClient {
|
export namespace LSPClient {
|
||||||
const log = Log.create({ service: "lsp.client" })
|
const log = Log.create({ service: "lsp.client" })
|
||||||
|
|
@ -35,7 +35,6 @@ export namespace LSPClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create(input: { serverID: string; server: LSPServer.Handle; root: string }) {
|
export async function create(input: { serverID: string; server: LSPServer.Handle; root: string }) {
|
||||||
const app = App.info()
|
|
||||||
const l = log.clone().tag("serverID", input.serverID)
|
const l = log.clone().tag("serverID", input.serverID)
|
||||||
l.info("starting client")
|
l.info("starting client")
|
||||||
|
|
||||||
|
|
@ -123,7 +122,7 @@ export namespace LSPClient {
|
||||||
},
|
},
|
||||||
notify: {
|
notify: {
|
||||||
async open(input: { path: string }) {
|
async open(input: { path: string }) {
|
||||||
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(app.path.cwd, input.path)
|
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Paths.directory, input.path)
|
||||||
const file = Bun.file(input.path)
|
const file = Bun.file(input.path)
|
||||||
const text = await file.text()
|
const text = await file.text()
|
||||||
const version = files[input.path]
|
const version = files[input.path]
|
||||||
|
|
@ -155,7 +154,7 @@ export namespace LSPClient {
|
||||||
return diagnostics
|
return diagnostics
|
||||||
},
|
},
|
||||||
async waitForDiagnostics(input: { path: string }) {
|
async waitForDiagnostics(input: { path: string }) {
|
||||||
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(app.path.cwd, input.path)
|
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Paths.directory, input.path)
|
||||||
log.info("waiting for diagnostics", input)
|
log.info("waiting for diagnostics", input)
|
||||||
let unsub: () => void
|
let unsub: () => void
|
||||||
return await withTimeout(
|
return await withTimeout(
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ export namespace LSP {
|
||||||
const result: LSPClient.Info[] = []
|
const result: LSPClient.Info[] = []
|
||||||
for (const server of Object.values(LSPServer)) {
|
for (const server of Object.values(LSPServer)) {
|
||||||
if (server.extensions.length && !server.extensions.includes(extension)) continue
|
if (server.extensions.length && !server.extensions.includes(extension)) continue
|
||||||
const root = await server.root(file, App.info())
|
const root = await server.root(file)
|
||||||
if (!root) continue
|
if (!root) continue
|
||||||
if (s.broken.has(root + server.id)) continue
|
if (s.broken.has(root + server.id)) continue
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ export namespace LSP {
|
||||||
result.push(match)
|
result.push(match)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const handle = await server.spawn(App.info(), root)
|
const handle = await server.spawn(root)
|
||||||
if (!handle) continue
|
if (!handle) continue
|
||||||
const client = await LSPClient.create({
|
const client = await LSPClient.create({
|
||||||
serverID: server.id,
|
serverID: server.id,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { spawn, type ChildProcessWithoutNullStreams } from "child_process"
|
import { spawn, type ChildProcessWithoutNullStreams } from "child_process"
|
||||||
import type { App } from "../app/app"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
|
|
@ -7,6 +6,7 @@ import { BunProc } from "../bun"
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export namespace LSPServer {
|
export namespace LSPServer {
|
||||||
const log = Log.create({ service: "lsp.server" })
|
const log = Log.create({ service: "lsp.server" })
|
||||||
|
|
@ -16,18 +16,18 @@ export namespace LSPServer {
|
||||||
initialization?: Record<string, any>
|
initialization?: Record<string, any>
|
||||||
}
|
}
|
||||||
|
|
||||||
type RootFunction = (file: string, app: App.Info) => Promise<string | undefined>
|
type RootFunction = (file: string) => Promise<string | undefined>
|
||||||
|
|
||||||
const NearestRoot = (patterns: string[]): RootFunction => {
|
const NearestRoot = (patterns: string[]): RootFunction => {
|
||||||
return async (file, app) => {
|
return async (file) => {
|
||||||
const files = Filesystem.up({
|
const files = Filesystem.up({
|
||||||
targets: patterns,
|
targets: patterns,
|
||||||
start: path.dirname(file),
|
start: path.dirname(file),
|
||||||
stop: app.path.root,
|
stop: Paths.worktree,
|
||||||
})
|
})
|
||||||
const first = await files.next()
|
const first = await files.next()
|
||||||
await files.return()
|
await files.return()
|
||||||
if (!first.value) return app.path.root
|
if (!first.value) return Paths.worktree
|
||||||
return path.dirname(first.value)
|
return path.dirname(first.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,15 +37,15 @@ export namespace LSPServer {
|
||||||
extensions: string[]
|
extensions: string[]
|
||||||
global?: boolean
|
global?: boolean
|
||||||
root: RootFunction
|
root: RootFunction
|
||||||
spawn(app: App.Info, root: string): Promise<Handle | undefined>
|
spawn(root: string): Promise<Handle | undefined>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Typescript: Info = {
|
export const Typescript: Info = {
|
||||||
id: "typescript",
|
id: "typescript",
|
||||||
root: NearestRoot(["tsconfig.json", "package.json", "jsconfig.json"]),
|
root: NearestRoot(["tsconfig.json", "package.json", "jsconfig.json"]),
|
||||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
|
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
|
||||||
async spawn(app, root) {
|
async spawn(root) {
|
||||||
const tsserver = await Bun.resolve("typescript/lib/tsserver.js", app.path.cwd).catch(() => {})
|
const tsserver = await Bun.resolve("typescript/lib/tsserver.js", Paths.directory).catch(() => {})
|
||||||
if (!tsserver) return
|
if (!tsserver) return
|
||||||
const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], {
|
const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], {
|
||||||
cwd: root,
|
cwd: root,
|
||||||
|
|
@ -67,13 +67,13 @@ export namespace LSPServer {
|
||||||
|
|
||||||
export const Gopls: Info = {
|
export const Gopls: Info = {
|
||||||
id: "golang",
|
id: "golang",
|
||||||
root: async (file, app) => {
|
root: async (file) => {
|
||||||
const work = await NearestRoot(["go.work"])(file, app)
|
const work = await NearestRoot(["go.work"])(file)
|
||||||
if (work) return work
|
if (work) return work
|
||||||
return NearestRoot(["go.mod", "go.sum"])(file, app)
|
return NearestRoot(["go.mod", "go.sum"])(file)
|
||||||
},
|
},
|
||||||
extensions: [".go"],
|
extensions: [".go"],
|
||||||
async spawn(_, root) {
|
async spawn(root) {
|
||||||
let bin = Bun.which("gopls", {
|
let bin = Bun.which("gopls", {
|
||||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||||
})
|
})
|
||||||
|
|
@ -109,7 +109,7 @@ export namespace LSPServer {
|
||||||
id: "ruby-lsp",
|
id: "ruby-lsp",
|
||||||
root: NearestRoot(["Gemfile"]),
|
root: NearestRoot(["Gemfile"]),
|
||||||
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
||||||
async spawn(_, root) {
|
async spawn(root) {
|
||||||
let bin = Bun.which("ruby-lsp", {
|
let bin = Bun.which("ruby-lsp", {
|
||||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||||
})
|
})
|
||||||
|
|
@ -149,7 +149,7 @@ export namespace LSPServer {
|
||||||
id: "pyright",
|
id: "pyright",
|
||||||
extensions: [".py", ".pyi"],
|
extensions: [".py", ".pyi"],
|
||||||
root: NearestRoot(["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]),
|
root: NearestRoot(["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"]),
|
||||||
async spawn(_, root) {
|
async spawn(root) {
|
||||||
const proc = spawn(BunProc.which(), ["x", "pyright-langserver", "--stdio"], {
|
const proc = spawn(BunProc.which(), ["x", "pyright-langserver", "--stdio"], {
|
||||||
cwd: root,
|
cwd: root,
|
||||||
env: {
|
env: {
|
||||||
|
|
@ -167,7 +167,7 @@ export namespace LSPServer {
|
||||||
id: "elixir-ls",
|
id: "elixir-ls",
|
||||||
extensions: [".ex", ".exs"],
|
extensions: [".ex", ".exs"],
|
||||||
root: NearestRoot(["mix.exs", "mix.lock"]),
|
root: NearestRoot(["mix.exs", "mix.lock"]),
|
||||||
async spawn(_, root) {
|
async spawn(root) {
|
||||||
let binary = Bun.which("elixir-ls")
|
let binary = Bun.which("elixir-ls")
|
||||||
if (!binary) {
|
if (!binary) {
|
||||||
const elixirLsPath = path.join(Global.Path.bin, "elixir-ls")
|
const elixirLsPath = path.join(Global.Path.bin, "elixir-ls")
|
||||||
|
|
@ -222,7 +222,7 @@ export namespace LSPServer {
|
||||||
id: "zls",
|
id: "zls",
|
||||||
extensions: [".zig", ".zon"],
|
extensions: [".zig", ".zon"],
|
||||||
root: NearestRoot(["build.zig"]),
|
root: NearestRoot(["build.zig"]),
|
||||||
async spawn(_, root) {
|
async spawn(root) {
|
||||||
let bin = Bun.which("zls", {
|
let bin = Bun.which("zls", {
|
||||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||||
})
|
})
|
||||||
|
|
@ -327,7 +327,7 @@ export namespace LSPServer {
|
||||||
id: "csharp",
|
id: "csharp",
|
||||||
root: NearestRoot([".sln", ".csproj", "global.json"]),
|
root: NearestRoot([".sln", ".csproj", "global.json"]),
|
||||||
extensions: [".cs"],
|
extensions: [".cs"],
|
||||||
async spawn(_, root) {
|
async spawn(root) {
|
||||||
let bin = Bun.which("csharp-ls", {
|
let bin = Bun.which("csharp-ls", {
|
||||||
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
PATH: process.env["PATH"] + ":" + Global.Path.bin,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ export namespace Project {
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
worktree: z.string(),
|
worktree: z.string(),
|
||||||
vcs: z.literal("git").optional(),
|
vcs: z.literal("git").optional(),
|
||||||
|
time: z.object({
|
||||||
|
created: z.number(),
|
||||||
|
initialized: z.number().optional(),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
|
|
@ -25,9 +29,12 @@ export namespace Project {
|
||||||
const git = await matches.next().then((x) => x.value)
|
const git = await matches.next().then((x) => x.value)
|
||||||
await matches.return()
|
await matches.return()
|
||||||
if (!git) {
|
if (!git) {
|
||||||
await StorageNext.write(["project", "global"], {
|
await StorageNext.write<Info>(["project", "global"], {
|
||||||
id: "global",
|
id: "global",
|
||||||
worktree: "/",
|
worktree: "/",
|
||||||
|
time: {
|
||||||
|
created: Date.now(),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -56,9 +63,19 @@ export namespace Project {
|
||||||
id,
|
id,
|
||||||
worktree,
|
worktree,
|
||||||
vcs: "git",
|
vcs: "git",
|
||||||
|
time: {
|
||||||
|
created: Date.now(),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export async function setInitialized() {
|
||||||
|
const project = use()
|
||||||
|
await StorageNext.update<Info>(["project", project.id], (draft) => {
|
||||||
|
draft.time.initialized = Date.now()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function list() {
|
export async function list() {
|
||||||
await init()
|
await init()
|
||||||
const keys = await StorageNext.list(["project"])
|
const keys = await StorageNext.list(["project"])
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { Mode } from "../session/mode"
|
||||||
import { callTui, TuiRoute } from "./tui"
|
import { callTui, TuiRoute } from "./tui"
|
||||||
import { Permission } from "../permission"
|
import { Permission } from "../permission"
|
||||||
import { lazy } from "../util/lazy"
|
import { lazy } from "../util/lazy"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
400: {
|
400: {
|
||||||
|
|
@ -692,10 +693,9 @@ export namespace Server {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const app = App.info()
|
|
||||||
const pattern = c.req.valid("query").pattern
|
const pattern = c.req.valid("query").pattern
|
||||||
const result = await Ripgrep.search({
|
const result = await Ripgrep.search({
|
||||||
cwd: app.path.cwd,
|
cwd: Paths.directory,
|
||||||
pattern,
|
pattern,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
})
|
})
|
||||||
|
|
@ -726,9 +726,8 @@ export namespace Server {
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const query = c.req.valid("query").query
|
const query = c.req.valid("query").query
|
||||||
const app = App.info()
|
|
||||||
const result = await Ripgrep.files({
|
const result = await Ripgrep.files({
|
||||||
cwd: app.path.cwd,
|
cwd: Paths.directory,
|
||||||
query,
|
query,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -161,10 +161,9 @@ export namespace Session {
|
||||||
)
|
)
|
||||||
|
|
||||||
export async function create(parentID?: string) {
|
export async function create(parentID?: string) {
|
||||||
const app = App.info()
|
|
||||||
return createNext({
|
return createNext({
|
||||||
parentID,
|
parentID,
|
||||||
directory: app.path.cwd,
|
directory: Paths.directory,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -443,7 +442,6 @@ export namespace Session {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = App.info()
|
|
||||||
const userParts = await Promise.all(
|
const userParts = await Promise.all(
|
||||||
input.parts.map(async (part): Promise<MessageV2.Part[]> => {
|
input.parts.map(async (part): Promise<MessageV2.Part[]> => {
|
||||||
if (part.type === "file") {
|
if (part.type === "file") {
|
||||||
|
|
@ -714,8 +712,8 @@ export namespace Session {
|
||||||
system,
|
system,
|
||||||
mode: inputMode,
|
mode: inputMode,
|
||||||
path: {
|
path: {
|
||||||
cwd: app.path.cwd,
|
cwd: Paths.directory,
|
||||||
root: app.path.root,
|
root: Paths.worktree,
|
||||||
},
|
},
|
||||||
cost: 0,
|
cost: 0,
|
||||||
tokens: {
|
tokens: {
|
||||||
|
|
@ -869,8 +867,8 @@ export namespace Session {
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
system,
|
system,
|
||||||
path: {
|
path: {
|
||||||
cwd: app.path.cwd,
|
cwd: Paths.directory,
|
||||||
root: app.path.root,
|
root: Paths.worktree,
|
||||||
},
|
},
|
||||||
cost: 0,
|
cost: 0,
|
||||||
tokens: {
|
tokens: {
|
||||||
|
|
@ -1266,7 +1264,6 @@ export namespace Session {
|
||||||
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
|
||||||
const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id)
|
const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id)
|
||||||
const model = await Provider.getModel(input.providerID, input.modelID)
|
const model = await Provider.getModel(input.providerID, input.modelID)
|
||||||
const app = App.info()
|
|
||||||
const system = [
|
const system = [
|
||||||
...SystemPrompt.summarize(input.providerID),
|
...SystemPrompt.summarize(input.providerID),
|
||||||
...(await SystemPrompt.environment()),
|
...(await SystemPrompt.environment()),
|
||||||
|
|
@ -1280,8 +1277,8 @@ export namespace Session {
|
||||||
system,
|
system,
|
||||||
mode: "build",
|
mode: "build",
|
||||||
path: {
|
path: {
|
||||||
cwd: app.path.cwd,
|
cwd: Paths.directory,
|
||||||
root: app.path.root,
|
root: Paths.worktree,
|
||||||
},
|
},
|
||||||
summary: true,
|
summary: true,
|
||||||
cost: 0,
|
cost: 0,
|
||||||
|
|
@ -1395,7 +1392,6 @@ export namespace Session {
|
||||||
providerID: string
|
providerID: string
|
||||||
messageID: string
|
messageID: string
|
||||||
}) {
|
}) {
|
||||||
const app = App.info()
|
|
||||||
await Session.chat({
|
await Session.chat({
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
messageID: input.messageID,
|
messageID: input.messageID,
|
||||||
|
|
@ -1405,10 +1401,10 @@ export namespace Session {
|
||||||
{
|
{
|
||||||
id: Identifier.ascending("part"),
|
id: Identifier.ascending("part"),
|
||||||
type: "text",
|
type: "text",
|
||||||
text: PROMPT_INITIALIZE.replace("${path}", app.path.root),
|
text: PROMPT_INITIALIZE.replace("${path}", Paths.worktree),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
await App.initialize()
|
await Project.setInitialized()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { App } from "../app/app"
|
|
||||||
import { Ripgrep } from "../file/ripgrep"
|
import { Ripgrep } from "../file/ripgrep"
|
||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
|
|
@ -13,6 +12,8 @@ import PROMPT_GEMINI from "./prompt/gemini.txt"
|
||||||
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
|
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
|
||||||
import PROMPT_SUMMARIZE from "./prompt/summarize.txt"
|
import PROMPT_SUMMARIZE from "./prompt/summarize.txt"
|
||||||
import PROMPT_TITLE from "./prompt/title.txt"
|
import PROMPT_TITLE from "./prompt/title.txt"
|
||||||
|
import { Project } from "../project/project"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export namespace SystemPrompt {
|
export namespace SystemPrompt {
|
||||||
export function header(providerID: string) {
|
export function header(providerID: string) {
|
||||||
|
|
@ -27,21 +28,21 @@ export namespace SystemPrompt {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function environment() {
|
export async function environment() {
|
||||||
const app = App.info()
|
const project = Project.use()
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
`Here is some useful information about the environment you are running in:`,
|
`Here is some useful information about the environment you are running in:`,
|
||||||
`<env>`,
|
`<env>`,
|
||||||
` Working directory: ${app.path.cwd}`,
|
` Working directory: ${Paths.directory}`,
|
||||||
` Is directory a git repo: ${app.git ? "yes" : "no"}`,
|
` Is directory a git repo: ${project.vcs === "git" ? "yes" : "no"}`,
|
||||||
` Platform: ${process.platform}`,
|
` Platform: ${process.platform}`,
|
||||||
` Today's date: ${new Date().toDateString()}`,
|
` Today's date: ${new Date().toDateString()}`,
|
||||||
`</env>`,
|
`</env>`,
|
||||||
`<project>`,
|
`<project>`,
|
||||||
` ${
|
` ${
|
||||||
app.git
|
project.vcs === "git"
|
||||||
? await Ripgrep.tree({
|
? await Ripgrep.tree({
|
||||||
cwd: app.path.cwd,
|
cwd: Paths.directory,
|
||||||
limit: 200,
|
limit: 200,
|
||||||
})
|
})
|
||||||
: ""
|
: ""
|
||||||
|
|
@ -58,12 +59,11 @@ export namespace SystemPrompt {
|
||||||
]
|
]
|
||||||
|
|
||||||
export async function custom() {
|
export async function custom() {
|
||||||
const { cwd, root } = App.info().path
|
|
||||||
const config = await Config.get()
|
const config = await Config.get()
|
||||||
const paths = new Set<string>()
|
const paths = new Set<string>()
|
||||||
|
|
||||||
for (const item of CUSTOM_FILES) {
|
for (const item of CUSTOM_FILES) {
|
||||||
const matches = await Filesystem.findUp(item, cwd, root)
|
const matches = await Filesystem.findUp(item, Paths.directory, Paths.worktree)
|
||||||
matches.forEach((path) => paths.add(path))
|
matches.forEach((path) => paths.add(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ export namespace SystemPrompt {
|
||||||
|
|
||||||
if (config.instructions) {
|
if (config.instructions) {
|
||||||
for (const instruction of config.instructions) {
|
for (const instruction of config.instructions) {
|
||||||
const matches = await Filesystem.globUp(instruction, cwd, root).catch(() => [])
|
const matches = await Filesystem.globUp(instruction, Paths.directory, Paths.worktree).catch(() => [])
|
||||||
matches.forEach((path) => paths.add(path))
|
matches.forEach((path) => paths.add(path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import { Log } from "../util/log"
|
||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
|
import { Project } from "../project/project"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export namespace Snapshot {
|
export namespace Snapshot {
|
||||||
const log = Log.create({ service: "snapshot" })
|
const log = Log.create({ service: "snapshot" })
|
||||||
|
|
@ -25,8 +27,8 @@ export namespace Snapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function track() {
|
export async function track() {
|
||||||
const app = App.info()
|
const project = Project.use()
|
||||||
if (!app.git) return
|
if (project.vcs !== "git") return
|
||||||
const cfg = await Config.get()
|
const cfg = await Config.get()
|
||||||
if (cfg.snapshot === false) return
|
if (cfg.snapshot === false) return
|
||||||
const git = gitdir()
|
const git = gitdir()
|
||||||
|
|
@ -35,14 +37,14 @@ export namespace Snapshot {
|
||||||
.env({
|
.env({
|
||||||
...process.env,
|
...process.env,
|
||||||
GIT_DIR: git,
|
GIT_DIR: git,
|
||||||
GIT_WORK_TREE: app.path.root,
|
GIT_WORK_TREE: Paths.worktree,
|
||||||
})
|
})
|
||||||
.quiet()
|
.quiet()
|
||||||
.nothrow()
|
.nothrow()
|
||||||
log.info("initialized")
|
log.info("initialized")
|
||||||
}
|
}
|
||||||
await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
|
await $`git --git-dir ${git} add .`.quiet().cwd(Paths.directory).nothrow()
|
||||||
const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(app.path.cwd).nothrow().text()
|
const hash = await $`git --git-dir ${git} write-tree`.quiet().cwd(Paths.directory).nothrow().text()
|
||||||
return hash.trim()
|
return hash.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,10 +55,9 @@ export namespace Snapshot {
|
||||||
export type Patch = z.infer<typeof Patch>
|
export type Patch = z.infer<typeof Patch>
|
||||||
|
|
||||||
export async function patch(hash: string): Promise<Patch> {
|
export async function patch(hash: string): Promise<Patch> {
|
||||||
const app = App.info()
|
|
||||||
const git = gitdir()
|
const git = gitdir()
|
||||||
await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
|
await $`git --git-dir ${git} add .`.quiet().cwd(Paths.directory).nothrow()
|
||||||
const files = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.cwd(app.path.cwd).text()
|
const files = await $`git --git-dir ${git} diff --name-only ${hash} -- .`.cwd(Paths.directory).text()
|
||||||
return {
|
return {
|
||||||
hash,
|
hash,
|
||||||
files: files
|
files: files
|
||||||
|
|
@ -64,17 +65,16 @@ export namespace Snapshot {
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((x) => x.trim())
|
.map((x) => x.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((x) => path.join(app.path.cwd, x)),
|
.map((x) => path.join(Paths.directory, x)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function restore(snapshot: string) {
|
export async function restore(snapshot: string) {
|
||||||
log.info("restore", { commit: snapshot })
|
log.info("restore", { commit: snapshot })
|
||||||
const app = App.info()
|
|
||||||
const git = gitdir()
|
const git = gitdir()
|
||||||
await $`git --git-dir=${git} read-tree ${snapshot} && git --git-dir=${git} checkout-index -a -f`
|
await $`git --git-dir=${git} read-tree ${snapshot} && git --git-dir=${git} checkout-index -a -f`
|
||||||
.quiet()
|
.quiet()
|
||||||
.cwd(app.path.root)
|
.cwd(Paths.worktree)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function revert(patches: Patch[]) {
|
export async function revert(patches: Patch[]) {
|
||||||
|
|
@ -86,7 +86,7 @@ export namespace Snapshot {
|
||||||
log.info("reverting", { file, hash: item.hash })
|
log.info("reverting", { file, hash: item.hash })
|
||||||
const result = await $`git --git-dir=${git} checkout ${item.hash} -- ${file}`
|
const result = await $`git --git-dir=${git} checkout ${item.hash} -- ${file}`
|
||||||
.quiet()
|
.quiet()
|
||||||
.cwd(App.info().path.root)
|
.cwd(Paths.worktree)
|
||||||
.nothrow()
|
.nothrow()
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
log.info("file not found in history, deleting", { file })
|
log.info("file not found in history, deleting", { file })
|
||||||
|
|
@ -98,14 +98,13 @@ export namespace Snapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function diff(hash: string) {
|
export async function diff(hash: string) {
|
||||||
const app = App.info()
|
|
||||||
const git = gitdir()
|
const git = gitdir()
|
||||||
const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(app.path.root).text()
|
const result = await $`git --git-dir=${git} diff ${hash} -- .`.quiet().cwd(Paths.worktree).text()
|
||||||
return result.trim()
|
return result.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
function gitdir() {
|
function gitdir() {
|
||||||
const app = App.info()
|
const project = Project.use()
|
||||||
return path.join(app.path.data, "snapshots")
|
return path.join(Global.Path.data, "snapshot", project.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { exec } from "child_process"
|
||||||
import { text } from "stream/consumers"
|
import { text } from "stream/consumers"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import DESCRIPTION from "./bash.txt"
|
import DESCRIPTION from "./bash.txt"
|
||||||
import { App } from "../app/app"
|
|
||||||
import { Permission } from "../permission"
|
import { Permission } from "../permission"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
|
|
@ -11,6 +10,7 @@ import { lazy } from "../util/lazy"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { Wildcard } from "../util/wildcard"
|
import { Wildcard } from "../util/wildcard"
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
const MAX_OUTPUT_LENGTH = 30000
|
const MAX_OUTPUT_LENGTH = 30000
|
||||||
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
||||||
|
|
@ -39,7 +39,6 @@ export const BashTool = Tool.define("bash", {
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
|
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
|
||||||
const app = App.info()
|
|
||||||
const cfg = await Config.get()
|
const cfg = await Config.get()
|
||||||
const tree = await parser().then((p) => p.parse(params.command))
|
const tree = await parser().then((p) => p.parse(params.command))
|
||||||
const permissions = (() => {
|
const permissions = (() => {
|
||||||
|
|
@ -83,9 +82,9 @@ export const BashTool = Tool.define("bash", {
|
||||||
.text()
|
.text()
|
||||||
.then((x) => x.trim())
|
.then((x) => x.trim())
|
||||||
log.info("resolved path", { arg, resolved })
|
log.info("resolved path", { arg, resolved })
|
||||||
if (resolved && !Filesystem.contains(app.path.cwd, resolved)) {
|
if (resolved && !Filesystem.contains(Paths.directory, resolved)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`This command references paths outside of ${app.path.cwd} so it is not allowed to be executed.`,
|
`This command references paths outside of ${Paths.directory} so it is not allowed to be executed.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +123,7 @@ export const BashTool = Tool.define("bash", {
|
||||||
}
|
}
|
||||||
|
|
||||||
const process = exec(params.command, {
|
const process = exec(params.command, {
|
||||||
cwd: app.path.cwd,
|
cwd: Paths.directory,
|
||||||
signal: ctx.abort,
|
signal: ctx.abort,
|
||||||
maxBuffer: MAX_OUTPUT_LENGTH,
|
maxBuffer: MAX_OUTPUT_LENGTH,
|
||||||
timeout,
|
timeout,
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@ import { LSP } from "../lsp"
|
||||||
import { createTwoFilesPatch } from "diff"
|
import { createTwoFilesPatch } from "diff"
|
||||||
import { Permission } from "../permission"
|
import { Permission } from "../permission"
|
||||||
import DESCRIPTION from "./edit.txt"
|
import DESCRIPTION from "./edit.txt"
|
||||||
import { App } from "../app/app"
|
|
||||||
import { File } from "../file"
|
import { File } from "../file"
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import { FileTime } from "../file/time"
|
import { FileTime } from "../file/time"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export const EditTool = Tool.define("edit", {
|
export const EditTool = Tool.define("edit", {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -34,9 +34,8 @@ export const EditTool = Tool.define("edit", {
|
||||||
throw new Error("oldString and newString must be different")
|
throw new Error("oldString and newString must be different")
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = App.info()
|
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Paths.directory, params.filePath)
|
||||||
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
|
if (!Filesystem.contains(Paths.directory, filePath)) {
|
||||||
if (!Filesystem.contains(app.path.cwd, filePath)) {
|
|
||||||
throw new Error(`File ${filePath} is not in the current working directory`)
|
throw new Error(`File ${filePath} is not in the current working directory`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +120,7 @@ export const EditTool = Tool.define("edit", {
|
||||||
diagnostics,
|
diagnostics,
|
||||||
diff,
|
diff,
|
||||||
},
|
},
|
||||||
title: `${path.relative(app.path.root, filePath)}`,
|
title: `${path.relative(Paths.worktree, filePath)}`,
|
||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { App } from "../app/app"
|
|
||||||
import DESCRIPTION from "./glob.txt"
|
import DESCRIPTION from "./glob.txt"
|
||||||
import { Ripgrep } from "../file/ripgrep"
|
import { Ripgrep } from "../file/ripgrep"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export const GlobTool = Tool.define("glob", {
|
export const GlobTool = Tool.define("glob", {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -17,9 +17,8 @@ export const GlobTool = Tool.define("glob", {
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
async execute(params) {
|
async execute(params) {
|
||||||
const app = App.info()
|
let search = params.path ?? Paths.directory
|
||||||
let search = params.path ?? app.path.cwd
|
search = path.isAbsolute(search) ? search : path.resolve(Paths.directory, search)
|
||||||
search = path.isAbsolute(search) ? search : path.resolve(app.path.cwd, search)
|
|
||||||
|
|
||||||
const limit = 100
|
const limit = 100
|
||||||
const files = []
|
const files = []
|
||||||
|
|
@ -55,7 +54,7 @@ export const GlobTool = Tool.define("glob", {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: path.relative(app.path.root, search),
|
title: path.relative(Paths.worktree, search),
|
||||||
metadata: {
|
metadata: {
|
||||||
count: files.length,
|
count: files.length,
|
||||||
truncated,
|
truncated,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { App } from "../app/app"
|
|
||||||
import { Ripgrep } from "../file/ripgrep"
|
import { Ripgrep } from "../file/ripgrep"
|
||||||
|
|
||||||
import DESCRIPTION from "./grep.txt"
|
import DESCRIPTION from "./grep.txt"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export const GrepTool = Tool.define("grep", {
|
export const GrepTool = Tool.define("grep", {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -17,8 +17,7 @@ export const GrepTool = Tool.define("grep", {
|
||||||
throw new Error("pattern is required")
|
throw new Error("pattern is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = App.info()
|
const searchPath = params.path || Paths.directory
|
||||||
const searchPath = params.path || app.path.cwd
|
|
||||||
|
|
||||||
const rgPath = await Ripgrep.filepath()
|
const rgPath = await Ripgrep.filepath()
|
||||||
const args = ["-n", params.pattern]
|
const args = ["-n", params.pattern]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { App } from "../app/app"
|
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import DESCRIPTION from "./ls.txt"
|
import DESCRIPTION from "./ls.txt"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export const IGNORE_PATTERNS = [
|
export const IGNORE_PATTERNS = [
|
||||||
"node_modules/",
|
"node_modules/",
|
||||||
|
|
@ -40,8 +40,7 @@ export const ListTool = Tool.define("list", {
|
||||||
ignore: z.array(z.string()).describe("List of glob patterns to ignore").optional(),
|
ignore: z.array(z.string()).describe("List of glob patterns to ignore").optional(),
|
||||||
}),
|
}),
|
||||||
async execute(params) {
|
async execute(params) {
|
||||||
const app = App.info()
|
const searchPath = path.resolve(Paths.directory, params.path || ".")
|
||||||
const searchPath = path.resolve(app.path.cwd, params.path || ".")
|
|
||||||
|
|
||||||
const glob = new Bun.Glob("**/*")
|
const glob = new Bun.Glob("**/*")
|
||||||
const files = []
|
const files = []
|
||||||
|
|
@ -102,7 +101,7 @@ export const ListTool = Tool.define("list", {
|
||||||
const output = `${searchPath}/\n` + renderDir(".", 0)
|
const output = `${searchPath}/\n` + renderDir(".", 0)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: path.relative(app.path.root, searchPath),
|
title: path.relative(Paths.worktree, searchPath),
|
||||||
metadata: {
|
metadata: {
|
||||||
count: files.length,
|
count: files.length,
|
||||||
truncated: files.length >= LIMIT,
|
truncated: files.length >= LIMIT,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { z } from "zod"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
import { App } from "../app/app"
|
|
||||||
import DESCRIPTION from "./lsp-diagnostics.txt"
|
import DESCRIPTION from "./lsp-diagnostics.txt"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export const LspDiagnosticTool = Tool.define("lsp_diagnostics", {
|
export const LspDiagnosticTool = Tool.define("lsp_diagnostics", {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -11,13 +11,12 @@ export const LspDiagnosticTool = Tool.define("lsp_diagnostics", {
|
||||||
path: z.string().describe("The path to the file to get diagnostics."),
|
path: z.string().describe("The path to the file to get diagnostics."),
|
||||||
}),
|
}),
|
||||||
execute: async (args) => {
|
execute: async (args) => {
|
||||||
const app = App.info()
|
const normalized = path.isAbsolute(args.path) ? args.path : path.join(Paths.directory, args.path)
|
||||||
const normalized = path.isAbsolute(args.path) ? args.path : path.join(app.path.cwd, args.path)
|
|
||||||
await LSP.touchFile(normalized, true)
|
await LSP.touchFile(normalized, true)
|
||||||
const diagnostics = await LSP.diagnostics()
|
const diagnostics = await LSP.diagnostics()
|
||||||
const file = diagnostics[normalized]
|
const file = diagnostics[normalized]
|
||||||
return {
|
return {
|
||||||
title: path.relative(app.path.root, normalized),
|
title: path.relative(Paths.worktree, normalized),
|
||||||
metadata: {
|
metadata: {
|
||||||
diagnostics,
|
diagnostics,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { z } from "zod"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
import { App } from "../app/app"
|
|
||||||
import DESCRIPTION from "./lsp-hover.txt"
|
import DESCRIPTION from "./lsp-hover.txt"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export const LspHoverTool = Tool.define("lsp_hover", {
|
export const LspHoverTool = Tool.define("lsp_hover", {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -13,8 +13,7 @@ export const LspHoverTool = Tool.define("lsp_hover", {
|
||||||
character: z.number().describe("The character number to get diagnostics."),
|
character: z.number().describe("The character number to get diagnostics."),
|
||||||
}),
|
}),
|
||||||
execute: async (args) => {
|
execute: async (args) => {
|
||||||
const app = App.info()
|
const file = path.isAbsolute(args.file) ? args.file : path.join(Paths.directory, args.file)
|
||||||
const file = path.isAbsolute(args.file) ? args.file : path.join(app.path.cwd, args.file)
|
|
||||||
await LSP.touchFile(file, true)
|
await LSP.touchFile(file, true)
|
||||||
const result = await LSP.hover({
|
const result = await LSP.hover({
|
||||||
...args,
|
...args,
|
||||||
|
|
@ -22,7 +21,7 @@ export const LspHoverTool = Tool.define("lsp_hover", {
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: path.relative(app.path.root, file) + ":" + args.line + ":" + args.character,
|
title: path.relative(Paths.worktree, file) + ":" + args.line + ":" + args.character,
|
||||||
metadata: {
|
metadata: {
|
||||||
result,
|
result,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { EditTool } from "./edit"
|
||||||
import DESCRIPTION from "./multiedit.txt"
|
import DESCRIPTION from "./multiedit.txt"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export const MultiEditTool = Tool.define("multiedit", {
|
export const MultiEditTool = Tool.define("multiedit", {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -35,9 +36,8 @@ export const MultiEditTool = Tool.define("multiedit", {
|
||||||
)
|
)
|
||||||
results.push(result)
|
results.push(result)
|
||||||
}
|
}
|
||||||
const app = App.info()
|
|
||||||
return {
|
return {
|
||||||
title: path.relative(app.path.root, params.filePath),
|
title: path.relative(Paths.worktree, params.filePath),
|
||||||
metadata: {
|
metadata: {
|
||||||
results: results.map((r) => r.metadata),
|
results: results.map((r) => r.metadata),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { FileTime } from "../file/time"
|
||||||
import DESCRIPTION from "./read.txt"
|
import DESCRIPTION from "./read.txt"
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
const DEFAULT_READ_LIMIT = 2000
|
const DEFAULT_READ_LIMIT = 2000
|
||||||
const MAX_LINE_LENGTH = 2000
|
const MAX_LINE_LENGTH = 2000
|
||||||
|
|
@ -23,8 +24,7 @@ export const ReadTool = Tool.define("read", {
|
||||||
if (!path.isAbsolute(filepath)) {
|
if (!path.isAbsolute(filepath)) {
|
||||||
filepath = path.join(process.cwd(), filepath)
|
filepath = path.join(process.cwd(), filepath)
|
||||||
}
|
}
|
||||||
const app = App.info()
|
if (!Filesystem.contains(Paths.directory, filepath)) {
|
||||||
if (!Filesystem.contains(app.path.cwd, filepath)) {
|
|
||||||
throw new Error(`File ${filepath} is not in the current working directory`)
|
throw new Error(`File ${filepath} is not in the current working directory`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ export const ReadTool = Tool.define("read", {
|
||||||
FileTime.read(ctx.sessionID, filepath)
|
FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: path.relative(App.info().path.root, filepath),
|
title: path.relative(Paths.worktree, filepath),
|
||||||
output,
|
output,
|
||||||
metadata: {
|
metadata: {
|
||||||
preview,
|
preview,
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ import { Tool } from "./tool"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
import { Permission } from "../permission"
|
import { Permission } from "../permission"
|
||||||
import DESCRIPTION from "./write.txt"
|
import DESCRIPTION from "./write.txt"
|
||||||
import { App } from "../app/app"
|
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import { File } from "../file"
|
import { File } from "../file"
|
||||||
import { FileTime } from "../file/time"
|
import { FileTime } from "../file/time"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { Filesystem } from "../util/filesystem"
|
import { Filesystem } from "../util/filesystem"
|
||||||
|
import { Paths } from "../project/path"
|
||||||
|
|
||||||
export const WriteTool = Tool.define("write", {
|
export const WriteTool = Tool.define("write", {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
|
|
@ -18,9 +18,8 @@ export const WriteTool = Tool.define("write", {
|
||||||
content: z.string().describe("The content to write to the file"),
|
content: z.string().describe("The content to write to the file"),
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
const app = App.info()
|
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Paths.directory, params.filePath)
|
||||||
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
|
if (!Filesystem.contains(Paths.directory, filepath)) {
|
||||||
if (!Filesystem.contains(app.path.cwd, filepath)) {
|
|
||||||
throw new Error(`File ${filepath} is not in the current working directory`)
|
throw new Error(`File ${filepath} is not in the current working directory`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,7 +61,7 @@ export const WriteTool = Tool.define("write", {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: path.relative(app.path.root, filepath),
|
title: path.relative(Paths.worktree, filepath),
|
||||||
metadata: {
|
metadata: {
|
||||||
diagnostics,
|
diagnostics,
|
||||||
filepath,
|
filepath,
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ func WithBackgroundColor(color compat.AdaptiveColor) renderingOption {
|
||||||
func WithNoBorder() renderingOption {
|
func WithNoBorder() renderingOption {
|
||||||
return func(c *blockRenderer) {
|
return func(c *blockRenderer) {
|
||||||
c.border = false
|
c.border = false
|
||||||
|
c.paddingLeft++
|
||||||
|
c.paddingRight++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -302,7 +304,7 @@ func renderText(
|
||||||
return renderContentBlock(
|
return renderContentBlock(
|
||||||
app,
|
app,
|
||||||
content,
|
content,
|
||||||
width+2,
|
width,
|
||||||
WithNoBorder(),
|
WithNoBorder(),
|
||||||
WithBackgroundColor(t.Background()),
|
WithBackgroundColor(t.Background()),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue