ring/platforms/opencode/plugin/loaders/placeholder-utils.ts
Gandalf f85aa61440
feat: add OpenCode platform integration (consolidate ring-for-opencode)
Move OpenCode runtime plugin and installer into Ring monorepo under platforms/opencode/.
The installer reads skills, agents, and commands directly from the Ring monorepo's
canonical directories (default/, dev-team/, pm-team/, etc.) — zero asset duplication.

What's included:
- installer.sh: reads from Ring dirs, applies frontmatter/tool transforms, installs to ~/.config/opencode/
- plugin/: TypeScript runtime (RingUnifiedPlugin) with hooks, lifecycle, loaders
- src/: CLI (doctor, config-manager)
- prompts/: session-start and context-injection templates
- standards/: coding standards (from dev-team/docs/)
- ring.jsonc: default config with full 86-skill/35-agent/33-command inventory

What's NOT included (intentionally):
- assets/ directory: eliminated, content comes from Ring monorepo
- scripts/codereview/: eliminated, replaced by mithril
- using-ring-opencode skill: uses canonical using-ring instead

Transforms applied by installer:
- Agent: type→mode, strip version/changelog/output_schema/input_schema
- Skill: keep name+description frontmatter, body unchanged
- Command: strip argument-hint (unsupported by OpenCode)
- All: normalize tool names (Bash→bash, Read→read, etc.)
- All: strip Model Requirement sections from agents

Replaces: LerianStudio/ring-for-opencode repository
Generated-by: Gandalf
AI-Model: claude-opus-4
2026-03-07 22:46:47 -03:00

81 lines
2.4 KiB
TypeScript

/**
* Placeholder Expansion Utilities
*
* Expands template placeholders in markdown content before passing to OpenCode.
*
* Supported placeholders:
* - {OPENCODE_CONFIG} - Expands to the OpenCode config directory path
*/
import { homedir } from "node:os"
import { isAbsolute, join } from "node:path"
/**
* Named constant for the placeholder string.
* Using a constant improves maintainability and ensures consistency.
*/
export const OPENCODE_CONFIG_PLACEHOLDER = "{OPENCODE_CONFIG}"
/**
* Get the OpenCode config directory path.
*
* Respects OPENCODE_CONFIG_DIR environment variable if set,
* falls back to XDG_CONFIG_HOME/opencode if set and absolute,
* otherwise defaults to ~/.config/opencode (following XDG standards).
*
* @returns The absolute path to OpenCode's config directory
* @throws Error if home directory cannot be determined
*/
export function getOpenCodeConfigDir(): string {
// Priority 1: Explicit OPENCODE_CONFIG_DIR
if (process.env.OPENCODE_CONFIG_DIR) {
return process.env.OPENCODE_CONFIG_DIR
}
// Priority 2: XDG_CONFIG_HOME (must be absolute path per XDG spec)
// Matches validation in config/loader.ts:196
const xdgConfigHome = process.env.XDG_CONFIG_HOME
if (xdgConfigHome && isAbsolute(xdgConfigHome)) {
return join(xdgConfigHome, "opencode")
}
// Priority 3: Default to ~/.config/opencode
const home = homedir()
if (!home) {
throw new Error(
"Cannot determine home directory. Set OPENCODE_CONFIG_DIR or HOME environment variable.",
)
}
return join(home, ".config", "opencode")
}
/**
* Expand placeholders in markdown content.
*
* Currently supports:
* - {OPENCODE_CONFIG} -> actual config directory path
*
* @param content - The markdown content with placeholders
* @returns Content with all placeholders expanded to their actual values
*
* @example
* ```typescript
* const content = "Read from {OPENCODE_CONFIG}/standards/golang.md"
* const expanded = expandPlaceholders(content)
* // Result: "Read from /Users/john/.config/opencode/standards/golang.md"
* ```
*/
export function expandPlaceholders(content: string): string {
// Input validation: handle null/undefined/non-string gracefully
if (typeof content !== "string") {
return ""
}
if (!content) {
return ""
}
const configDir = getOpenCodeConfigDir()
// Replace all occurrences of {OPENCODE_CONFIG} with the actual path
return content.replace(new RegExp(OPENCODE_CONFIG_PLACEHOLDER, "g"), configDir)
}