feat(core): add native skill activation

This commit is contained in:
Dax Raad 2026-06-30 01:14:44 -04:00
commit 23adaaaeab
26 changed files with 814 additions and 76 deletions

View file

@ -6,26 +6,112 @@ import { define } from "./internal"
import { Effect } from "effect"
import { AbsolutePath } from "../schema"
import { SkillV2 } from "../skill"
import { InstallationChannel, InstallationVersion } from "../installation/version"
import { Config } from "../config"
import { Location } from "../location"
import { FSUtil } from "../fs-util"
import os from "os"
import path from "path"
import { fileURLToPath } from "url"
import customizeOpencodeContent from "./skill/customize-opencode.md" with { type: "text" }
import reportContent from "./skill/report.md" with { type: "text" }
export const CustomizeOpencodeContent = customizeOpencodeContent
export const ReportContent = reportContent
const CUSTOMIZE_OPENCODE_DESCRIPTION =
"Use ONLY when the user is editing or creating opencode's own configuration: opencode.json, opencode.jsonc, files under .opencode/, or files under ~/.config/opencode/. Also use when creating or fixing opencode agents, subagents, commands, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself."
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: "skill",
effect: Effect.fn(function* (ctx) {
const reportContent = yield* reportContentWithDiagnostics()
yield* ctx.skill.transform((draft) => {
draft.source(
SkillV2.EmbeddedSource.make({
type: "embedded",
skill: SkillV2.Info.make({
name: "customize-opencode",
description:
"Use ONLY when the user is editing or creating opencode's own configuration: opencode.json, opencode.jsonc, files under .opencode/, or files under ~/.config/opencode/. Also use when creating or fixing opencode agents, subagents, commands, skills, plugins, MCP servers, or permission rules. Do not use for the user's own application code, or for any project that is not configuring opencode itself.",
description: CUSTOMIZE_OPENCODE_DESCRIPTION,
location: AbsolutePath.make("/builtin/customize-opencode.md"),
content: CustomizeOpencodeContent,
}),
}),
)
draft.source(
SkillV2.EmbeddedSource.make({
type: "embedded",
skill: SkillV2.Info.make({
name: "report",
description: REPORT_DESCRIPTION,
slash: true,
location: AbsolutePath.make("/builtin/report.md"),
content: reportContent,
}),
}),
)
})
}),
})
const reportContentWithDiagnostics = Effect.fn("SkillPlugin.reportContentWithDiagnostics")(function* () {
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: ${InstallationVersion}`,
`- install/channel: ${InstallationChannel}`,
`- 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
}),
)
}
return fs
.glob("{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"
}

View file

@ -0,0 +1,125 @@
<!--
Built-in skill. Name and description are registered in code at
packages/core/src/plugin/skill.ts. The body below becomes the skill's
content.
-->
# Report an opencode Issue
Use this skill when the user wants to report an opencode issue or bug. Your job
is to turn the user's problem into a useful GitHub issue with standard
diagnostics plus the context needed to reproduce and resolve it.
## Workflow
1. Collect the standard diagnostics below.
2. Ask only for missing details that are necessary to reproduce or understand
impact.
3. Draft the issue in the standard format below.
4. Publish it with GitHub CLI after the user confirms the title and body.
Do not publish an issue without user confirmation. If GitHub CLI is not
installed or not authenticated, explain the blocker and provide the exact issue
title/body for the user.
## Standard Diagnostics
Collect these values when possible:
- opencode version: run `opencode --version` or `opencode2 --version`,
depending on the executable in use.
- Operating system: run `uname -a` on Unix-like systems, or `ver` on Windows.
- Terminal: inspect `$TERM`, `$TERM_PROGRAM`, `$COLORTERM`, and any obvious
terminal app context the user provides.
- Shell: inspect `$SHELL` on Unix-like systems, or `%COMSPEC%`/`$ComSpec` on
Windows when relevant.
- Install/channel context: include whether this appears to be local, dev, beta,
or release if the version output or environment reveals it.
- Active plugins: inspect opencode config for configured plugins when possible.
Check likely config locations such as `opencode.json`, `opencode.jsonc`,
`.opencode/opencode.json`, and `~/.config/opencode/opencode.json`. Record
configured plugin entries, local plugin files under `.opencode/plugin/` or
`.opencode/plugins/`, and note if plugin status could not be determined.
If a diagnostic command fails, include `Unavailable` with the reason instead of
guessing.
## User-Specific Context
Capture the details that make the issue actionable:
- What the user was trying to do.
- What happened.
- What the user expected to happen.
- Reproduction steps, ideally minimal and numbered.
- Relevant logs, stack traces, screenshots, terminal output, or config snippets.
- Whether the issue is reproducible consistently, intermittently, or only once.
- Recent changes that may be related, such as updating opencode, changing
config, installing a plugin, changing terminal, or switching workspace.
- Workarounds tried and whether they helped.
Avoid pasting secrets. Redact tokens, API keys, private URLs, usernames, and
project-specific confidential data unless the user explicitly says it is safe.
## Issue Format
Use this exact structure unless the repository issue template requires
otherwise:
```markdown
## Summary
<!-- One or two sentences describing the bug and impact. -->
## Environment
- opencode version: <!-- value or Unavailable: reason -->
- OS: <!-- value or Unavailable: reason -->
- Terminal: <!-- value or Unavailable: reason -->
- Shell: <!-- value or Unavailable: reason -->
- Install/channel: <!-- value or Unavailable: reason -->
- Active plugins: <!-- list, none found, or Unavailable: reason -->
## Reproduction
1. <!-- step -->
2. <!-- step -->
3. <!-- step -->
## Expected Behavior
<!-- What should have happened. -->
## Actual Behavior
<!-- What happened instead. Include exact errors when available. -->
## Additional Context
<!-- Logs, config snippets, screenshots, frequency, workarounds, related notes. -->
```
Keep the title short and searchable. Prefer the form:
```text
<area>: <specific failure or symptom>
```
Examples: `tui: skills dialog crashes outside location provider`,
`cli: local service config writes release filename`.
## Publishing With GitHub CLI
Use GitHub CLI from the repository checkout when available:
```sh
gh issue create --title "<title>" --body-file <file>
```
Write the body to a temporary markdown file first so quoting, newlines, logs,
and code fences are preserved. If the issue belongs in a specific repository,
use `--repo owner/name`. If labels are obvious and the repo accepts them, add
`--label bug`; otherwise omit labels rather than guessing.
After publishing, report the created issue URL to the user and mention any
diagnostics that were unavailable.