Прокси (server.mjs) вставляет panel.js в страницы веб-интерфейса и отдаёт /__ctx/api/* из opencode.db. Теперь он поднимается плагином context-panel.v2.ts внутри процесса OpenCode, а не отдельным Node-процессом. - server.mjs: экспортирует startPanel(); SQLite — bun:sqlite под Bun, node:sqlite под Node; ручной запуск `node server.mjs …` сохранён. - context-panel.v2.ts: один прокси на процесс (globalThis), при dispose не закрывается; порты из CTX_PANEL_LISTEN / CTX_PANEL_TARGET, выключение — OPENCODE_CONTEXT_PANEL=0, журнал — log/context-panel.log. Плагины грузятся лениво (при первом запросе к API проекта), поэтому обёртка должна сама дёрнуть API после старта `opencode serve`.
63 lines
3.2 KiB
TypeScript
63 lines
3.2 KiB
TypeScript
/**
|
|
* context-panel для OpenCode 2: панель «Токены» в веб-интерфейсе — без отдельного процесса.
|
|
*
|
|
* Плагин поднимает внутри процесса OpenCode тот же прокси, что server.mjs (startPanel): он
|
|
* слушает CTX_PANEL_LISTEN (4196), пропускает всё на CTX_PANEL_TARGET (4197, порт самого
|
|
* `opencode serve`) и вставляет в страницы panel.js. Встроить панель прямо в порт OpenCode
|
|
* нельзя: веб-интерфейс зашит в бинарник, а свои HTTP-маршруты плагину добавить нечем.
|
|
*
|
|
* Что учтено (проверено на 2.1.3):
|
|
* - плагин грузится лениво — при первом запросе к API проекта, а не при старте `serve`.
|
|
* Поэтому обёртка после старта сама дёргает API (bin/opencode-web), иначе на 4196 пусто;
|
|
* - модуль импортируется заново для каждого проекта — прокси один на процесс (globalThis);
|
|
* - при dispose прокси не закрывается: браузер ходит через него, и без него OpenCode не
|
|
* получил бы запрос, который загрузит плагин снова. Живёт, пока жив процесс.
|
|
*
|
|
* Выключить: OPENCODE_CONTEXT_PANEL=0. База — $XDG_DATA_HOME/opencode/opencode.db или CTX_PANEL_DB.
|
|
* Подключение: файл в <config>/opencode/plugins/ — bin/opencode-web делает ссылку сам.
|
|
*/
|
|
|
|
import { appendFile, mkdir } from "node:fs/promises"
|
|
import { homedir } from "node:os"
|
|
import { dirname, join } from "node:path"
|
|
import { realpathSync } from "node:fs"
|
|
import { fileURLToPath, pathToFileURL } from "node:url"
|
|
|
|
const LOG_DIR = join(process.env.XDG_DATA_HOME || join(homedir(), ".local/share"), "opencode/log")
|
|
const LOG_FILE = join(LOG_DIR, "context-panel.log")
|
|
|
|
async function log(line: string) {
|
|
try {
|
|
await mkdir(LOG_DIR, { recursive: true })
|
|
await appendFile(LOG_FILE, `${new Date().toISOString()} ${line}\n`)
|
|
} catch {}
|
|
}
|
|
|
|
const KEY = Symbol.for("opencode-plugins.context-panel")
|
|
const state = globalThis as { [KEY]?: Promise<unknown> }
|
|
|
|
function start() {
|
|
// Файл подключён ссылкой из plugins/ — server.mjs ищем рядом с настоящим файлом
|
|
const here = dirname(realpathSync(fileURLToPath(import.meta.url)))
|
|
const opts = {
|
|
listen: process.env.CTX_PANEL_LISTEN ?? 4196,
|
|
target: process.env.CTX_PANEL_TARGET ?? 4197,
|
|
...(process.env.CTX_PANEL_DB ? { db: process.env.CTX_PANEL_DB } : {}),
|
|
}
|
|
return import(pathToFileURL(join(here, "server.mjs")).href)
|
|
.then((m) => m.startPanel(opts))
|
|
.then(
|
|
() => log(`listening ${opts.listen} → ${opts.target}`),
|
|
(e) => log(`не запустилась: ${e?.code ?? e?.message ?? e}`),
|
|
)
|
|
}
|
|
|
|
export default {
|
|
id: "context-panel",
|
|
setup: async () => {
|
|
if (process.env.OPENCODE_CONTEXT_PANEL === "0") return
|
|
state[KEY] ??= start()
|
|
await state[KEY]
|
|
},
|
|
}
|