@khalilgharbaoui/opencode-claude-code-plugin 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +61 -26
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/skills/claude-code-plugin/SKILL.md +441 -0
package/README.md
CHANGED
|
@@ -553,6 +553,20 @@ Notes:
|
|
|
553
553
|
|
|
554
554
|
Fully restart opencode after upgrading to load the command and runtime changes. Other providers do not gain Claude's native side-question behavior from this command.
|
|
555
555
|
|
|
556
|
+
## Configuration skill
|
|
557
|
+
|
|
558
|
+
The package includes a `claude-code-plugin` skill so your agent can configure it without asking you to navigate all its options. Ask, for example:
|
|
559
|
+
|
|
560
|
+
```text
|
|
561
|
+
Use the claude-code-plugin skill to configure a work account and idle worker cleanup.
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
It covers accounts, models and agent effort, proxy tools, permissions, MCP/skill bridging, timeouts, logging, upgrades and troubleshooting. It directs the agent to preserve JSONC comments, change only requested settings, validate the result, protect credentials and ask before paid probes or broader permissions.
|
|
565
|
+
|
|
566
|
+
The plugin registers the bundled directory with opencode's `skills.paths`, making it available to other providers too on supporting opencode versions. For ordinary headless Claude turns it also loads through Claude's native Skill tool as `opencode-skills:claude-code-plugin`, even when `bridgeOpencodeSkills` is off. This requires CLI `--plugin-dir` support; interactive transport, compaction and direct `doGenerate` calls do not load the native bridge.
|
|
567
|
+
|
|
568
|
+
No separate skill installation or copying is needed. It ships with each package version, so upgrading updates the reference. Fully restart opencode to load it. `test-configure-skill.ts` checks coverage of provider/logging options, model ids, proxy tools and environment variables; maintainers must update behavior and default guidance in the same change as the implementation.
|
|
569
|
+
|
|
556
570
|
## Skill bridge
|
|
557
571
|
|
|
558
572
|
opencode and Claude Code use the same on-disk skill format, a `<name>/SKILL.md` whose frontmatter carries `name` and `description`, but they read from different directories. opencode looks in `.opencode/skills/` and `~/.config/opencode/skills/`; the Claude CLI looks in `~/.claude/skills/` and its own plugins. So opencode advertises your skills in the system prompt it forwards, the model calls `Skill("browser-automation")`, and Claude answers `Unknown skill`.
|
|
@@ -568,7 +582,7 @@ Claude can invoke them with the Skill tool or as `/opencode-skills:<name>`. `--p
|
|
|
568
582
|
|
|
569
583
|
Discovery order, first match wins: `.opencode/skills/` walking up from the working directory, then `~/.opencode/skills/`, then `$OPENCODE_CONFIG_DIR/skills/`, then `~/.config/opencode/skills/`. A project skill shadows a global one of the same name. If the skill set is unchanged the staged directory is reused between spawns.
|
|
570
584
|
|
|
571
|
-
|
|
585
|
+
Bridging **your own skills is off by default** here, unlike on the fork it came from: every bridged skill is also listed in the system prompt opencode already forwards, so a large skill set is paid for twice on every turn. Turn it on when you see `Unknown skill`. The bundled configuration skill is loaded independently of this opt-in. The native bridge is not used for compaction or interactive transport, or on a Claude CLI without `--plugin-dir` (the plugin probes `claude --help` and logs a notice).
|
|
572
586
|
|
|
573
587
|
This bridge was written by [@broskees](https://github.com/broskees) (Joseph Roberts) on his fork and absorbed here with credit; see [Credits](#credits).
|
|
574
588
|
|
package/dist/index.d.ts
CHANGED
|
@@ -78,6 +78,10 @@ type OpenCodeConfig = {
|
|
|
78
78
|
models?: Record<string, unknown>;
|
|
79
79
|
}>;
|
|
80
80
|
agent?: Record<string, Record<string, unknown>>;
|
|
81
|
+
skills?: {
|
|
82
|
+
paths?: string[];
|
|
83
|
+
urls?: string[];
|
|
84
|
+
};
|
|
81
85
|
};
|
|
82
86
|
/**
|
|
83
87
|
* Bus events surface to plugins. Shape mirrors what opencode core publishes
|
package/dist/index.js
CHANGED
|
@@ -447,10 +447,13 @@ function detectCliSupportsFlag(cliPath, flag) {
|
|
|
447
447
|
if (cached) return cached;
|
|
448
448
|
const promise = (async () => {
|
|
449
449
|
try {
|
|
450
|
-
const
|
|
450
|
+
const execution = execFileAsync(cliPath, ["--help"], {
|
|
451
451
|
timeout: 5e3,
|
|
452
|
+
killSignal: "SIGKILL",
|
|
452
453
|
maxBuffer: 4 * 1024 * 1024
|
|
453
454
|
});
|
|
455
|
+
execution.child.stdin?.end();
|
|
456
|
+
const { stdout } = await execution;
|
|
454
457
|
return stdout.includes(flag);
|
|
455
458
|
} catch (err) {
|
|
456
459
|
log.warn("failed to probe claude cli flag support", {
|
|
@@ -3302,7 +3305,17 @@ import * as crypto2 from "crypto";
|
|
|
3302
3305
|
import * as fs3 from "fs";
|
|
3303
3306
|
import * as os2 from "os";
|
|
3304
3307
|
import * as path4 from "path";
|
|
3308
|
+
import { fileURLToPath } from "url";
|
|
3305
3309
|
var SKILL_PLUGIN_NAME = "opencode-skills";
|
|
3310
|
+
function bundledSkillsDir() {
|
|
3311
|
+
try {
|
|
3312
|
+
const here = fileURLToPath(import.meta.url);
|
|
3313
|
+
const dir = path4.resolve(path4.dirname(here), "..", "skills");
|
|
3314
|
+
return dirExists(dir) ? dir : null;
|
|
3315
|
+
} catch {
|
|
3316
|
+
return null;
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3306
3319
|
function dirExists(p) {
|
|
3307
3320
|
try {
|
|
3308
3321
|
return fs3.statSync(p).isDirectory();
|
|
@@ -3341,28 +3354,46 @@ function skillRoots(cwd) {
|
|
|
3341
3354
|
if (xdg) push(path4.join(xdg, "opencode", "skills"));
|
|
3342
3355
|
return roots;
|
|
3343
3356
|
}
|
|
3357
|
+
function collectSkills(root, claimed, found) {
|
|
3358
|
+
let entries;
|
|
3359
|
+
try {
|
|
3360
|
+
entries = fs3.readdirSync(root, { withFileTypes: true });
|
|
3361
|
+
} catch {
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
for (const entry of entries) {
|
|
3365
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3366
|
+
const name = entry.name;
|
|
3367
|
+
if (name.startsWith(".")) continue;
|
|
3368
|
+
if (claimed.has(name)) continue;
|
|
3369
|
+
const dir = path4.join(root, name);
|
|
3370
|
+
if (!fileExists(path4.join(dir, "SKILL.md"))) continue;
|
|
3371
|
+
claimed.add(name);
|
|
3372
|
+
found.push({ name, dir });
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
var byName = (a, b) => a.name.localeCompare(b.name);
|
|
3344
3376
|
function discoverOpencodeSkills(cwd) {
|
|
3345
3377
|
const found = [];
|
|
3346
3378
|
const claimed = /* @__PURE__ */ new Set();
|
|
3347
|
-
for (const root of skillRoots(cwd))
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
return found.sort((a, b) => a.name.localeCompare(b.name));
|
|
3379
|
+
for (const root of skillRoots(cwd)) collectSkills(root, claimed, found);
|
|
3380
|
+
return found.sort(byName);
|
|
3381
|
+
}
|
|
3382
|
+
function discoverBundledSkills() {
|
|
3383
|
+
const root = bundledSkillsDir();
|
|
3384
|
+
if (!root) return [];
|
|
3385
|
+
const found = [];
|
|
3386
|
+
collectSkills(root, /* @__PURE__ */ new Set(), found);
|
|
3387
|
+
return found.sort(byName);
|
|
3388
|
+
}
|
|
3389
|
+
function registerBundledSkillPath(config) {
|
|
3390
|
+
const dir = bundledSkillsDir();
|
|
3391
|
+
if (!dir) return false;
|
|
3392
|
+
config.skills ??= {};
|
|
3393
|
+
const paths = config.skills.paths ??= [];
|
|
3394
|
+
if (paths.some((p) => path4.resolve(p) === dir)) return false;
|
|
3395
|
+
paths.push(dir);
|
|
3396
|
+
return true;
|
|
3366
3397
|
}
|
|
3367
3398
|
function linkSkill(source, target) {
|
|
3368
3399
|
try {
|
|
@@ -3408,8 +3439,10 @@ function buildSkillPluginDir(skills) {
|
|
|
3408
3439
|
return root;
|
|
3409
3440
|
}
|
|
3410
3441
|
async function resolveSkillPluginDirs(opts) {
|
|
3411
|
-
|
|
3412
|
-
const
|
|
3442
|
+
const user = opts.enabled ? discoverOpencodeSkills(opts.cwd) : [];
|
|
3443
|
+
const claimed = new Set(user.map((s) => s.name));
|
|
3444
|
+
const bundled = discoverBundledSkills().filter((s) => !claimed.has(s.name));
|
|
3445
|
+
const skills = [...user, ...bundled].sort(byName);
|
|
3413
3446
|
if (skills.length === 0) return [];
|
|
3414
3447
|
const supported = await detectCliSupportsFlag(opts.cliPath, "--plugin-dir");
|
|
3415
3448
|
if (!supported) {
|
|
@@ -3424,6 +3457,7 @@ async function resolveSkillPluginDirs(opts) {
|
|
|
3424
3457
|
log.info("bridged opencode skills into claude", {
|
|
3425
3458
|
count: skills.length,
|
|
3426
3459
|
names: skills.map((s) => s.name),
|
|
3460
|
+
bundled: bundled.map((s) => s.name),
|
|
3427
3461
|
pluginDir: dir
|
|
3428
3462
|
});
|
|
3429
3463
|
return [dir];
|
|
@@ -7670,7 +7704,7 @@ import {
|
|
|
7670
7704
|
} from "fs";
|
|
7671
7705
|
import { homedir as homedir6 } from "os";
|
|
7672
7706
|
import { join as join8, resolve as resolve4 } from "path";
|
|
7673
|
-
import { fileURLToPath } from "url";
|
|
7707
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7674
7708
|
var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
|
|
7675
7709
|
var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
|
|
7676
7710
|
var alreadyRan = false;
|
|
@@ -7702,7 +7736,7 @@ function userIntendsToUseUnscoped() {
|
|
|
7702
7736
|
}
|
|
7703
7737
|
function ourLoadedDir() {
|
|
7704
7738
|
try {
|
|
7705
|
-
const filePath =
|
|
7739
|
+
const filePath = fileURLToPath2(import.meta.url);
|
|
7706
7740
|
return realpathSync(resolve4(filePath, "..", ".."));
|
|
7707
7741
|
} catch {
|
|
7708
7742
|
return null;
|
|
@@ -7776,12 +7810,12 @@ import { execFile as execFile2 } from "child_process";
|
|
|
7776
7810
|
import * as fs6 from "fs";
|
|
7777
7811
|
import * as path8 from "path";
|
|
7778
7812
|
import { promisify as promisify2 } from "util";
|
|
7779
|
-
import { fileURLToPath as
|
|
7813
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7780
7814
|
var cachedPluginVersion;
|
|
7781
7815
|
function pluginVersion() {
|
|
7782
7816
|
if (cachedPluginVersion) return cachedPluginVersion;
|
|
7783
7817
|
try {
|
|
7784
|
-
const here = path8.dirname(
|
|
7818
|
+
const here = path8.dirname(fileURLToPath3(import.meta.url));
|
|
7785
7819
|
const raw = fs6.readFileSync(path8.join(here, "..", "package.json"), "utf8");
|
|
7786
7820
|
const version = JSON.parse(raw).version;
|
|
7787
7821
|
cachedPluginVersion = typeof version === "string" ? version : "unknown";
|
|
@@ -8177,6 +8211,7 @@ var server = async (input) => {
|
|
|
8177
8211
|
return {
|
|
8178
8212
|
config: async (config) => {
|
|
8179
8213
|
if (registerSideQuestionCommand(config)) ownsSideQuestionCommand = true;
|
|
8214
|
+
registerBundledSkillPath(config);
|
|
8180
8215
|
config.provider ??= {};
|
|
8181
8216
|
await buildAgentRegistry(config);
|
|
8182
8217
|
const expanded = await expandAccountProviders(config);
|