mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-09 10:59:49 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 292a449632 |
@@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env bun
|
|
||||||
|
|
||||||
import { $ } from "bun"
|
|
||||||
|
|
||||||
// drizzle-kit check compares schema to migrations, exits non-zero if drift
|
|
||||||
const result = await $`bun drizzle-kit check`.quiet().nothrow()
|
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
|
||||||
console.error("Schema has changes not captured in migrations!")
|
|
||||||
console.error("Run: bun drizzle-kit generate")
|
|
||||||
console.error("")
|
|
||||||
console.error(result.stderr.toString())
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Migrations are up to date")
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/usr/bin/env bun
|
|
||||||
|
|
||||||
import path from "path"
|
|
||||||
const toDynamicallyImport = path.join(process.cwd(), process.argv[2])
|
|
||||||
await import(toDynamicallyImport)
|
|
||||||
console.log(performance.now())
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
#!/usr/bin/env bun
|
|
||||||
import * as path from "path"
|
|
||||||
import * as ts from "typescript"
|
|
||||||
|
|
||||||
const BASE_DIR = "/home/thdxr/dev/projects/anomalyco/opencode/packages/opencode"
|
|
||||||
|
|
||||||
// Get entry file from command line arg or use default
|
|
||||||
const ENTRY_FILE = process.argv[2] || "src/cli/cmd/tui/plugin/index.ts"
|
|
||||||
|
|
||||||
const visited = new Set<string>()
|
|
||||||
|
|
||||||
function resolveImport(importPath: string, fromFile: string): string | null {
|
|
||||||
if (importPath.startsWith("@/")) {
|
|
||||||
return path.join(BASE_DIR, "src", importPath.slice(2))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (importPath.startsWith("./") || importPath.startsWith("../")) {
|
|
||||||
const dir = path.dirname(fromFile)
|
|
||||||
return path.resolve(dir, importPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function isInternalImport(importPath: string): boolean {
|
|
||||||
return importPath.startsWith("@/") || importPath.startsWith("./") || importPath.startsWith("../")
|
|
||||||
}
|
|
||||||
|
|
||||||
async function tryExtensions(filePath: string): Promise<string | null> {
|
|
||||||
const extensions = [".ts", ".tsx", ".js", ".jsx"]
|
|
||||||
|
|
||||||
try {
|
|
||||||
const file = Bun.file(filePath)
|
|
||||||
const stat = await file.stat()
|
|
||||||
|
|
||||||
if (stat?.isDirectory()) {
|
|
||||||
for (const ext of extensions) {
|
|
||||||
const indexPath = path.join(filePath, "index" + ext)
|
|
||||||
const indexFile = Bun.file(indexPath)
|
|
||||||
if (await indexFile.exists()) return indexPath
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's a file
|
|
||||||
return filePath
|
|
||||||
} catch {
|
|
||||||
// Path doesn't exist, try adding extensions
|
|
||||||
for (const ext of extensions) {
|
|
||||||
const withExt = filePath + ext
|
|
||||||
const extFile = Bun.file(withExt)
|
|
||||||
if (await extFile.exists()) return withExt
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractImports(sourceFile: ts.SourceFile): string[] {
|
|
||||||
const imports: string[] = []
|
|
||||||
|
|
||||||
function visit(node: ts.Node) {
|
|
||||||
// import x from "path" or import { x } from "path"
|
|
||||||
if (ts.isImportDeclaration(node)) {
|
|
||||||
// Skip type-only imports
|
|
||||||
if (node.importClause?.isTypeOnly) return
|
|
||||||
|
|
||||||
const moduleSpec = node.moduleSpecifier
|
|
||||||
if (ts.isStringLiteral(moduleSpec)) {
|
|
||||||
imports.push(moduleSpec.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// export { x } from "path"
|
|
||||||
if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
||||||
if (ts.isStringLiteral(node.moduleSpecifier)) {
|
|
||||||
imports.push(node.moduleSpecifier.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dynamic import: import("path")
|
|
||||||
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
||||||
const arg = node.arguments[0]
|
|
||||||
if (arg && ts.isStringLiteral(arg)) {
|
|
||||||
imports.push(arg.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ts.forEachChild(node, visit)
|
|
||||||
}
|
|
||||||
|
|
||||||
visit(sourceFile)
|
|
||||||
return imports
|
|
||||||
}
|
|
||||||
|
|
||||||
async function traceFile(filePath: string, depth = 0): Promise<void> {
|
|
||||||
const normalizedPath = path.relative(BASE_DIR, filePath)
|
|
||||||
|
|
||||||
if (visited.has(filePath)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only trace TypeScript/JavaScript files
|
|
||||||
if (!filePath.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
visited.add(filePath)
|
|
||||||
console.log("\t".repeat(depth) + normalizedPath)
|
|
||||||
|
|
||||||
let content: string
|
|
||||||
try {
|
|
||||||
content = await Bun.file(filePath).text()
|
|
||||||
} catch {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true)
|
|
||||||
|
|
||||||
const imports = extractImports(sourceFile)
|
|
||||||
const internalImports = imports.filter(isInternalImport)
|
|
||||||
const externalImports = imports.filter((imp) => !isInternalImport(imp))
|
|
||||||
|
|
||||||
// Print external imports
|
|
||||||
for (const imp of externalImports) {
|
|
||||||
console.log("\t".repeat(depth + 1) + `[ext] ${imp}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const imp of internalImports) {
|
|
||||||
const resolved = resolveImport(imp, filePath)
|
|
||||||
if (!resolved) continue
|
|
||||||
|
|
||||||
const actualPath = await tryExtensions(resolved)
|
|
||||||
if (!actualPath) continue
|
|
||||||
|
|
||||||
await traceFile(actualPath, depth + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const entryPath = path.join(BASE_DIR, ENTRY_FILE)
|
|
||||||
|
|
||||||
// Check if file exists
|
|
||||||
const file = Bun.file(entryPath)
|
|
||||||
if (!(await file.exists())) {
|
|
||||||
console.error(`File not found: ${ENTRY_FILE}`)
|
|
||||||
console.error(`Resolved to: ${entryPath}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
await traceFile(entryPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch(console.error)
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import { OpenCode } from "@opencode-ai/core"
|
|
||||||
import { ReadTool } from "@opencode-ai/core/tools"
|
|
||||||
|
|
||||||
const opencode = OpenCode.make({})
|
|
||||||
|
|
||||||
opencode.tool.add(ReadTool)
|
|
||||||
|
|
||||||
opencode.tool.add({
|
|
||||||
name: "bash",
|
|
||||||
schema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
command: {
|
|
||||||
type: "string",
|
|
||||||
description: "The command to run.",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["command"],
|
|
||||||
},
|
|
||||||
execute(input, ctx) {},
|
|
||||||
})
|
|
||||||
|
|
||||||
opencode.auth.add({
|
|
||||||
provider: "openai",
|
|
||||||
type: "api",
|
|
||||||
value: process.env.OPENAI_API_KEY,
|
|
||||||
})
|
|
||||||
|
|
||||||
opencode.agent.add({
|
|
||||||
name: "build",
|
|
||||||
permissions: [],
|
|
||||||
model: {
|
|
||||||
id: "gpt-5-5",
|
|
||||||
provider: "openai",
|
|
||||||
variant: "xhigh",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const sessionID = await opencode.session.create({
|
|
||||||
agent: "build",
|
|
||||||
})
|
|
||||||
|
|
||||||
opencode.subscribe((event) => {
|
|
||||||
console.log(event)
|
|
||||||
})
|
|
||||||
|
|
||||||
await opencode.session.prompt({
|
|
||||||
sessionID,
|
|
||||||
text: "hey what is up",
|
|
||||||
})
|
|
||||||
|
|
||||||
await opencode.session.prompt({
|
|
||||||
sessionID,
|
|
||||||
text: "what is up with this",
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
mime: "image/png",
|
|
||||||
uri: "data:image/png;base64,xxxx",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
await opencode.session.wait()
|
|
||||||
|
|
||||||
console.log(await opencode.session.messages(sessionID))
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { createMemo, createResource } from "solid-js"
|
|
||||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
||||||
import { useDialog } from "@tui/ui/dialog"
|
|
||||||
import { useProject } from "@tui/context/project"
|
|
||||||
import { useSDK } from "@tui/context/sdk"
|
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
|
|
||||||
export function DialogTag(props: { onSelect?: (value: string) => void }) {
|
|
||||||
const sdk = useSDK()
|
|
||||||
const dialog = useDialog()
|
|
||||||
const project = useProject()
|
|
||||||
|
|
||||||
const [store] = createStore({
|
|
||||||
filter: "",
|
|
||||||
})
|
|
||||||
|
|
||||||
const [files] = createResource(
|
|
||||||
() => [store.filter],
|
|
||||||
async () => {
|
|
||||||
const result = await sdk.client.find.files({
|
|
||||||
query: store.filter,
|
|
||||||
workspace: project.workspace.current(),
|
|
||||||
})
|
|
||||||
if (result.error) return []
|
|
||||||
const sliced = (result.data ?? []).slice(0, 5)
|
|
||||||
return sliced
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const options = createMemo(() =>
|
|
||||||
(files() ?? []).map((file) => ({
|
|
||||||
value: file,
|
|
||||||
title: file,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DialogSelect
|
|
||||||
title="Autocomplete"
|
|
||||||
options={options()}
|
|
||||||
onSelect={(option) => {
|
|
||||||
props.onSelect?.(option.value)
|
|
||||||
dialog.clear()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
||||||
import { useRoute } from "@tui/context/route"
|
|
||||||
|
|
||||||
export function DialogSubagent(props: { sessionID: string }) {
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DialogSelect
|
|
||||||
title="Subagent Actions"
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
title: "Open",
|
|
||||||
value: "subagent.view",
|
|
||||||
description: "the subagent's session",
|
|
||||||
onSelect: (dialog) => {
|
|
||||||
route.navigate({
|
|
||||||
type: "session",
|
|
||||||
sessionID: props.sessionID,
|
|
||||||
})
|
|
||||||
dialog.clear()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import { createMemo, Match, onCleanup, onMount, Show, Switch } from "solid-js"
|
|
||||||
import { useTheme } from "../../context/theme"
|
|
||||||
import { useSync } from "../../context/sync"
|
|
||||||
import { useDirectory } from "../../context/directory"
|
|
||||||
import { useConnected } from "../../component/use-connected"
|
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
import { useRoute } from "../../context/route"
|
|
||||||
|
|
||||||
export function Footer() {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
const sync = useSync()
|
|
||||||
const route = useRoute()
|
|
||||||
const mcp = createMemo(() => Object.values(sync.data.mcp).filter((x) => x.status === "connected").length)
|
|
||||||
const mcpError = createMemo(() => Object.values(sync.data.mcp).some((x) => x.status === "failed"))
|
|
||||||
const lsp = createMemo(() => Object.keys(sync.data.lsp))
|
|
||||||
const permissions = createMemo(() => {
|
|
||||||
if (route.data.type !== "session") return []
|
|
||||||
return sync.data.permission[route.data.sessionID] ?? []
|
|
||||||
})
|
|
||||||
const directory = useDirectory()
|
|
||||||
const connected = useConnected()
|
|
||||||
|
|
||||||
const [store, setStore] = createStore({
|
|
||||||
welcome: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// Track all timeouts to ensure proper cleanup
|
|
||||||
const timeouts: ReturnType<typeof setTimeout>[] = []
|
|
||||||
|
|
||||||
function tick() {
|
|
||||||
if (connected()) return
|
|
||||||
if (!store.welcome) {
|
|
||||||
setStore("welcome", true)
|
|
||||||
timeouts.push(setTimeout(() => tick(), 5000))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (store.welcome) {
|
|
||||||
setStore("welcome", false)
|
|
||||||
timeouts.push(setTimeout(() => tick(), 10_000))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
timeouts.push(setTimeout(() => tick(), 10_000))
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
timeouts.forEach(clearTimeout)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<box flexDirection="row" justifyContent="space-between" gap={1} flexShrink={0}>
|
|
||||||
<text fg={theme.textMuted}>{directory()}</text>
|
|
||||||
<box gap={2} flexDirection="row" flexShrink={0}>
|
|
||||||
<Switch>
|
|
||||||
<Match when={store.welcome}>
|
|
||||||
<text fg={theme.text}>
|
|
||||||
Get started <span style={{ fg: theme.textMuted }}>/connect</span>
|
|
||||||
</text>
|
|
||||||
</Match>
|
|
||||||
<Match when={connected()}>
|
|
||||||
<Show when={permissions().length > 0}>
|
|
||||||
<text fg={theme.warning}>
|
|
||||||
<span style={{ fg: theme.warning }}>△</span> {permissions().length} Permission
|
|
||||||
{permissions().length > 1 ? "s" : ""}
|
|
||||||
</text>
|
|
||||||
</Show>
|
|
||||||
<text fg={theme.text}>
|
|
||||||
<span style={{ fg: lsp().length > 0 ? theme.success : theme.textMuted }}>•</span> {lsp().length} LSP
|
|
||||||
</text>
|
|
||||||
<Show when={mcp()}>
|
|
||||||
<text fg={theme.text}>
|
|
||||||
<Switch>
|
|
||||||
<Match when={mcpError()}>
|
|
||||||
<span style={{ fg: theme.error }}>⊙ </span>
|
|
||||||
</Match>
|
|
||||||
<Match when={true}>
|
|
||||||
<span style={{ fg: theme.success }}>⊙ </span>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
{mcp()} MCP
|
|
||||||
</text>
|
|
||||||
</Show>
|
|
||||||
<text fg={theme.textMuted}>/status</text>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import fs from "fs/promises"
|
|
||||||
import { Flock } from "@opencode-ai/core/util/flock"
|
|
||||||
|
|
||||||
type Msg = {
|
|
||||||
key: string
|
|
||||||
dir: string
|
|
||||||
staleMs?: number
|
|
||||||
timeoutMs?: number
|
|
||||||
baseDelayMs?: number
|
|
||||||
maxDelayMs?: number
|
|
||||||
holdMs?: number
|
|
||||||
ready?: string
|
|
||||||
active?: string
|
|
||||||
done?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep(ms: number) {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
setTimeout(resolve, ms)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function input() {
|
|
||||||
const raw = process.argv[2]
|
|
||||||
if (!raw) {
|
|
||||||
throw new Error("Missing flock worker input")
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.parse(raw) as Msg
|
|
||||||
}
|
|
||||||
|
|
||||||
async function job(input: Msg) {
|
|
||||||
if (input.ready) {
|
|
||||||
await fs.writeFile(input.ready, String(process.pid))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.active) {
|
|
||||||
await fs.writeFile(input.active, String(process.pid), { flag: "wx" })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (input.holdMs && input.holdMs > 0) {
|
|
||||||
await sleep(input.holdMs)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.done) {
|
|
||||||
await fs.appendFile(input.done, "1\n")
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (input.active) {
|
|
||||||
await fs.rm(input.active, { force: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const msg = input()
|
|
||||||
|
|
||||||
await Flock.withLock(msg.key, () => job(msg), {
|
|
||||||
dir: msg.dir,
|
|
||||||
staleMs: msg.staleMs,
|
|
||||||
timeoutMs: msg.timeoutMs,
|
|
||||||
baseDelayMs: msg.baseDelayMs,
|
|
||||||
maxDelayMs: msg.maxDelayMs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await main().catch((err) => {
|
|
||||||
const text = err instanceof Error ? (err.stack ?? err.message) : String(err)
|
|
||||||
process.stderr.write(text)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user