mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-05 23:59:56 +00:00
128 lines
4.8 KiB
TypeScript
128 lines
4.8 KiB
TypeScript
/// <reference path="../markdown.d.ts" />
|
|
|
|
export * as SkillPlugin from "./skill"
|
|
|
|
import { define, type Context } from "@opencode-ai/plugin/effect/plugin"
|
|
import { Effect } from "effect"
|
|
import { AbsolutePath } from "../schema"
|
|
import { Skill } from "../skill"
|
|
import { Config } from "../config"
|
|
import { Location } from "../location"
|
|
import { FSUtil } from "@opencode-ai/util/fs-util"
|
|
import os from "os"
|
|
import path from "path"
|
|
import { fileURLToPath } from "url"
|
|
import opencodeContent from "./skill/opencode.md" with { type: "text" }
|
|
import reportContent from "./skill/report.md" with { type: "text" }
|
|
|
|
export const OpencodeContent = opencodeContent
|
|
export const ReportContent = reportContent
|
|
|
|
export const OpencodeDescription =
|
|
"Use this skill for any question about OpenCode itself, including how OpenCode works, using or configuring it, migrating from V1 to V2, troubleshooting it, developing plugins or integrations, using the OpenCode SDK, clients, server, or API, and contributing to the OpenCode codebase. Also use it for OpenCode agents, commands, skills, tools, permissions, MCP servers, providers, models, themes, keybinds, formatters, the CLI, TUI, desktop app, and web app."
|
|
const REPORT_DESCRIPTION =
|
|
"Use when the user wants to report an opencode issue or bug. Collect standard diagnostics, add user-specific reproduction context, and publish the issue with GitHub CLI."
|
|
|
|
export const Plugin = define({
|
|
id: "opencode.skill",
|
|
effect: Effect.fn(function* (ctx) {
|
|
const reportContent = yield* reportContentWithDiagnostics(ctx.app)
|
|
yield* ctx.skill.transform((draft) => {
|
|
draft.source(
|
|
Skill.EmbeddedSource.make({
|
|
type: "embedded",
|
|
skill: Skill.Info.make({
|
|
id: Skill.ID.make("opencode"),
|
|
name: Skill.Name.make("OpenCode"),
|
|
description: OpencodeDescription,
|
|
location: AbsolutePath.make("/builtin/opencode.md"),
|
|
content: OpencodeContent,
|
|
}),
|
|
}),
|
|
)
|
|
draft.source(
|
|
Skill.EmbeddedSource.make({
|
|
type: "embedded",
|
|
skill: Skill.Info.make({
|
|
id: Skill.ID.make("report"),
|
|
name: Skill.Name.make("Report"),
|
|
description: REPORT_DESCRIPTION,
|
|
slash: true,
|
|
location: AbsolutePath.make("/builtin/report.md"),
|
|
content: reportContent,
|
|
}),
|
|
}),
|
|
)
|
|
})
|
|
}),
|
|
})
|
|
|
|
const reportContentWithDiagnostics = Effect.fn("SkillPlugin.reportContentWithDiagnostics")(function* (
|
|
app: Context["app"],
|
|
) {
|
|
const plugins = yield* configuredPlugins().pipe(Effect.orElseSucceed(() => ["Unavailable: failed to inspect config"]))
|
|
return [
|
|
ReportContent,
|
|
"",
|
|
"## Runtime Diagnostics Snapshot",
|
|
"",
|
|
"These values were captured when the built-in report skill was registered. Verify them before publishing.",
|
|
"",
|
|
`- opencode version: ${app.version}`,
|
|
`- install/channel: ${app.channel}`,
|
|
`- OS: ${os.type()} ${os.release()} (${os.platform()} ${os.arch()})`,
|
|
`- Terminal: ${terminal()}`,
|
|
`- Shell: ${shell()}`,
|
|
`- Active plugins: ${plugins.length === 0 ? "None found in config" : plugins.join(", ")}`,
|
|
].join("\n")
|
|
})
|
|
|
|
const configuredPlugins = Effect.fn("SkillPlugin.configuredPlugins")(function* () {
|
|
const config = yield* Config.Service
|
|
const fs = yield* FSUtil.Service
|
|
const location = yield* Location.Service
|
|
return yield* Effect.forEach(yield* config.entries(), (entry) => {
|
|
if (entry.type === "document") {
|
|
const directory = entry.path ? path.dirname(entry.path) : location.directory
|
|
return Effect.succeed(
|
|
(entry.info.plugins ?? []).map((item) => {
|
|
const ref = typeof item === "string" ? { package: item } : item
|
|
if (ref.package.startsWith("file://")) return fileURLToPath(ref.package)
|
|
if (ref.package.startsWith("./") || ref.package.startsWith("../")) return path.resolve(directory, ref.package)
|
|
return ref.package
|
|
}),
|
|
)
|
|
}
|
|
if (entry.type !== "directory") return Effect.succeed([])
|
|
return fs
|
|
.scan("{plugin,plugins}/*.{ts,js}", {
|
|
cwd: entry.path,
|
|
absolute: true,
|
|
include: "file",
|
|
dot: true,
|
|
symlink: true,
|
|
})
|
|
.pipe(Effect.orElseSucceed(() => []))
|
|
}).pipe(Effect.map((items) => items.flat().toSorted()))
|
|
})
|
|
|
|
function terminal() {
|
|
return (
|
|
[
|
|
process.env.TERM_PROGRAM ? `TERM_PROGRAM=${process.env.TERM_PROGRAM}` : undefined,
|
|
process.env.TERM ? `TERM=${process.env.TERM}` : undefined,
|
|
process.env.COLORTERM ? `COLORTERM=${process.env.COLORTERM}` : undefined,
|
|
]
|
|
.filter((item): item is string => item !== undefined)
|
|
.join(", ") || "Unavailable: terminal environment variables are not set"
|
|
)
|
|
}
|
|
|
|
function shell() {
|
|
return (
|
|
process.env.SHELL ??
|
|
process.env.ComSpec ??
|
|
process.env.COMSPEC ??
|
|
"Unavailable: shell environment variable is not set"
|
|
)
|
|
}
|