feat: publish ponytail as an installable npm package for OpenCode and Pi (#197)
* feat: register slash commands from .opencode/command/*.md Add parseCommandFile() to read frontmatter-described markdown files and wire them into opencode's config.command during init. Extend the config hook to scan .opencode/command/ and register each .md file as a named slash command. Update the plugin doc comment to reflect the npm install path (opencode-ponytail) vs the old relative path. * chore: rename package to opencode-ponytail Align package name with npm convention for opencode plugins. Update keywords to include opencode-plugin and opencode tags. Fix description back to original correct wording (grammatical regression introduced during editing). * chore: add npm metadata and publish workflow Add author, homepage, repository, bugs, main, exports, files, and publishConfig fields to package.json for npm publishing. Add .github/workflows/publish.yml to auto-publish to npm on version tags (v*) with provenance. * docs: add npm plugin install for opencode-ponytail --------- Co-authored-by: Emeriko <dietrich.gebert@gmail.com>
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
// ponytail — OpenCode plugin.
|
||||
//
|
||||
// Injects the ponytail ruleset into every chat's system prompt at the active
|
||||
// intensity, and persists /ponytail mode switches. Reuses the shared instruction
|
||||
// builder so Claude Code, Codex, pi, and OpenCode all read one source of truth.
|
||||
// intensity, persists /ponytail mode switches, and registers slash commands so
|
||||
// they work when the package is installed from npm. Reuses the shared
|
||||
// instruction builder so Claude Code, Codex, pi, and OpenCode all read one
|
||||
// source of truth.
|
||||
//
|
||||
// OpenCode loads this as a server plugin — add it to your opencode.json:
|
||||
// { "plugin": ["./.opencode/plugins/ponytail.mjs"] }
|
||||
// { "plugin": ["opencode-ponytail"] }
|
||||
|
||||
import { createRequire } from 'module';
|
||||
import fs from 'fs';
|
||||
@@ -40,6 +42,15 @@ function writeMode(mode) {
|
||||
fs.writeFileSync(statePath, mode);
|
||||
}
|
||||
|
||||
export function parseCommandFile(filePath) {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
// Tolerate CRLF: a Windows checkout (autocrlf) delivers \r\n, npm ships \n.
|
||||
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
||||
if (!match) return null;
|
||||
const description = match[1].match(/description:\s*(.+)/)?.[1]?.trim();
|
||||
return { description, template: match[2].trim() };
|
||||
}
|
||||
|
||||
export default async ({ client } = {}) => {
|
||||
const log = (level, message) => {
|
||||
try { client && client.app && client.app.log({ body: { service: 'ponytail', level, message } }); } catch (e) {}
|
||||
@@ -48,8 +59,18 @@ export default async ({ client } = {}) => {
|
||||
const ponytailSkillsDir = path.resolve(__dirname, '../../skills');
|
||||
|
||||
return {
|
||||
// Register skills directory so opencode discovers ponytail skills.
|
||||
// Register slash commands + skills directory.
|
||||
config: async (config) => {
|
||||
if (!config.command) config.command = {};
|
||||
const commandDir = path.join(__dirname, '..', 'command');
|
||||
try {
|
||||
for (const file of fs.readdirSync(commandDir).filter((f) => f.endsWith('.md'))) {
|
||||
const name = path.basename(file, '.md');
|
||||
const parsed = parseCommandFile(path.join(commandDir, file));
|
||||
if (parsed) config.command[name] = parsed;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
config.skills = config.skills || {};
|
||||
config.skills.paths = config.skills.paths || [];
|
||||
if (!config.skills.paths.includes(ponytailSkillsDir)) {
|
||||
|
||||
Reference in New Issue
Block a user