mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Fix OpenCode slash command bridge (#483)
Add a first-class OpenCode command bridge across builds, installs, updates, linked installs, and pinned shortcuts. Preserve current provider behavior while backfilling missing or drifted command files.\n\nAI assistance: contributor and maintainer work used AI tools as disclosed in the PR discussion and commits.
This commit is contained in:
@@ -20,6 +20,7 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { readSourceFiles, readPatterns, stashPerProjectArtifacts, restorePerProjectArtifacts } from './lib/utils.js';
|
||||
import { syncRootCommands } from './lib/root-commands-sync.mjs';
|
||||
import { createTransformer, PROVIDERS } from './lib/transformers/index.js';
|
||||
import { hooksJsonFor, buildClaudePluginHooksManifest } from './lib/transformers/hooks.js';
|
||||
import { createAllZips, createProviderZip } from './lib/zip.js';
|
||||
@@ -657,6 +658,11 @@ async function build() {
|
||||
}
|
||||
}
|
||||
|
||||
const syncedCommands = syncRootCommands(DIST_DIR, ROOT_DIR, syncConfigs);
|
||||
if (syncedCommands.length > 0) {
|
||||
console.log(`📟 Synced provider commands to: ${syncedCommands.join(', ')}`);
|
||||
}
|
||||
|
||||
const syncedHooks = syncRootHookManifests(ROOT_DIR);
|
||||
if (syncedHooks.length > 0) {
|
||||
console.log(`🪝 Synced hook manifests to: ${syncedHooks.join(', ')}`);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Mirror generated provider command files (e.g. OpenCode's
|
||||
* commands/impeccable.md) from dist/ into the tracked root harness folders.
|
||||
* Without this, the release sync ships skills/agents/hooks but no slash
|
||||
* command bridge, so direct GitHub, npx-skills, and submodule installs of
|
||||
* OpenCode stay bridge-less (#483). Per-entry copy like the skills sync:
|
||||
* the destination directory is never removed, so repo-local or pinned
|
||||
* command files are preserved.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export function syncRootCommands(distDir, rootDir, providers) {
|
||||
const synced = [];
|
||||
for (const { provider, configDir } of providers) {
|
||||
const src = path.join(distDir, provider, configDir, 'commands');
|
||||
if (!fs.existsSync(src)) continue;
|
||||
const dest = path.join(rootDir, configDir, 'commands');
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
||||
if (!entry.isFile()) continue;
|
||||
fs.copyFileSync(path.join(src, entry.name), path.join(dest, entry.name));
|
||||
}
|
||||
synced.push(configDir);
|
||||
}
|
||||
return synced;
|
||||
}
|
||||
@@ -379,6 +379,28 @@ export function createTransformer(config) {
|
||||
}
|
||||
}
|
||||
|
||||
// Ship an explicit slash-command surface for OpenCode. OpenCode registers
|
||||
// skill commands natively but its TUI autocomplete hides them by deliberate
|
||||
// design (anomalyco/opencode#25439); this file also pins execution policy
|
||||
// (agent: build, subtask: true) and routes through OpenCode's skill tool,
|
||||
// which resolves the skill base dir for any install scope. Menu visibility
|
||||
// is the only part contingent on OpenCode's design; the rest is intentional.
|
||||
// Schema restricted to what OpenCode recognises (description, agent, model,
|
||||
// variant, subtask).
|
||||
if (provider === 'opencode' && skills.length > 0) {
|
||||
const commandsDir = path.join(providerDir, `${configDir}/commands`);
|
||||
ensureDir(commandsDir);
|
||||
for (const skill of skills) {
|
||||
const bridgeBody = `Call skill({ name: "${skill.name}" }) and follow its \`Setup\` and \`Commands\` sections to handle $ARGUMENTS.\n`;
|
||||
const bridgeFrontmatter = generateYamlFrontmatter({
|
||||
description: skill.description,
|
||||
agent: 'build',
|
||||
subtask: true,
|
||||
});
|
||||
writeFile(path.join(commandsDir, `${skill.name}.md`), `${bridgeFrontmatter}\n${bridgeBody}`.replace(/\n+$/, '\n'));
|
||||
}
|
||||
}
|
||||
|
||||
if (config.agentFormat) {
|
||||
const agentsDir = path.join(providerDir, `${configDir}/agents`);
|
||||
for (const skill of skills) {
|
||||
|
||||
@@ -36,13 +36,16 @@ export const SUITES = {
|
||||
files: [
|
||||
'tests/build.test.js',
|
||||
'tests/cli-ignores.test.js',
|
||||
'tests/copy-provider-commands.test.js',
|
||||
'tests/windows-path-fix.test.js',
|
||||
'tests/lib/provider-blocks.test.js',
|
||||
'tests/lib/transformers/provider-blocks.test.js',
|
||||
'tests/lib/utils.test.js',
|
||||
'tests/lib/impeccable-config.test.js',
|
||||
'tests/lib/transformers/factory.test.js',
|
||||
'tests/lib/transformers/opencode-commands.test.js',
|
||||
'tests/lib/transformers/providers.test.js',
|
||||
'tests/root-commands-sync.test.js',
|
||||
'tests/skills-cli.test.js',
|
||||
'tests/validate-plugin-versions.test.js',
|
||||
'tests/validate-plugin-manifest.test.js',
|
||||
|
||||
Reference in New Issue
Block a user