Files
cleveragents-core/.opencode/skills/templating-vault/scripts/render.ts
freemo 1885990081
CI / build (push) Waiting to run
CI / docker (push) Blocked by required conditions
CI / helm (push) Waiting to run
CI / push-validation (push) Waiting to run
CI / status-check (push) Blocked by required conditions
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / security (push) Waiting to run
CI / quality (push) Waiting to run
CI / unit_tests (push) Waiting to run
CI / integration_tests (push) Waiting to run
CI / e2e_tests (push) Waiting to run
CI / coverage (push) Blocked by required conditions
CI / benchmark-publish (push) Waiting to run
build: auto opencode agents rewritten
2026-04-27 12:49:08 -04:00

180 lines
5.5 KiB
TypeScript

#!/usr/bin/env -S npx --yes tsx
// =============================================================================
// render.ts — Render a template with vault variable substitution
//
// Part of: .opencode/skills/templating-vault/scripts/
//
// Two modes:
// 1. File mode (--output-file): Renders template, writes to file, returns path
// 2. Prepare mode (--prepare --id): Renders template, stores in vault under
// prepared.<id>, returns only the ID. For vault-aware prompt-by-reference.
//
// USAGE:
// # File mode (vault-unaware agents)
// render.ts --template-key <key> --scope <tag> --output-file <path>
//
// # Prepare mode (vault-aware agents)
// render.ts --template-key <key> --scope <tag> --prepare --id <id>
//
// # Template from file instead of vault
// render.ts --template-file <path> --scope <tag> --output-file <path>
//
// OUTPUT (stdout): JSON result
// =============================================================================
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import yaml from 'js-yaml';
import {
parseCommonFlags, getFlag, hasFlag, output, log,
loadConfig, readValue, getValue, writeValue,
loadSchema, saveKeyMeta, listAllKeys, loadKeyMeta, readValue,
evaluateTemplate, atomicWrite,
type TemplateSchema, type KeyMeta,
} from './api.ts';
async function main(): Promise<void> {
const { config: cfgPath, storeDirs, rest } = parseCommonFlags(process.argv);
const config = loadConfig(cfgPath, storeDirs.length > 0 ? storeDirs : undefined);
const templateKey = getFlag(rest, '--template-key');
const templateFile = getFlag(rest, '--template-file');
const outputFile = getFlag(rest, '--output-file');
const prepare = hasFlag(rest, '--prepare');
let id = getFlag(rest, '--id');
if (!templateKey && !templateFile) {
output({ rendered: false, error: '--template-key or --template-file is required' });
process.exit(1);
}
// Auto-generate ID if --prepare but no --id
if (prepare && !id) {
id = `prep-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}
if (!prepare && !outputFile) {
output({ rendered: false, error: '--output-file is required when not using --prepare' });
process.exit(1);
}
// Load template text
let templateText: string;
let schema: TemplateSchema | null = null;
if (templateKey) {
const raw = readValue(config, templateKey);
if (raw === null) {
output({ rendered: false, error: `Template key "${templateKey}" not found in vault` });
process.exit(1);
}
templateText = raw;
schema = loadSchema(config, templateKey);
} else {
if (!fs.existsSync(templateFile!)) {
output({ rendered: false, error: `Template file "${templateFile}" not found` });
process.exit(1);
}
templateText = fs.readFileSync(templateFile!, 'utf-8');
}
// Collect variables for rendering
const variables: Record<string, unknown> = {};
const substituted: string[] = [];
const missing: string[] = [];
// Collect RAW values from all vault stores (skip templates to avoid recursion)
const allKeys = listAllKeys(config, '', true);
for (const k of allKeys) {
const meta = loadKeyMeta(config, k);
if (meta.template) continue; // Skip computed/template keys
const rawVal = readValue(config, k);
if (rawVal !== null) {
const nunjucksKey = k.replace(/\./g, '_');
variables[nunjucksKey] = rawVal;
const shortName = k.split('.').pop()!;
if (!(shortName in variables)) {
variables[shortName] = rawVal;
}
}
}
// Detect which placeholders are in the template
const placeholderRegex = /\{\{\s*(\w+)(?:\s*\|[^}]*)?\s*\}\}/g;
let match: RegExpExecArray | null;
const foundPlaceholders = new Set<string>();
while ((match = placeholderRegex.exec(templateText)) !== null) {
foundPlaceholders.add(match[1]);
}
for (const ph of foundPlaceholders) {
if (ph in variables) {
substituted.push(ph);
} else {
missing.push(ph);
}
}
// Render template
let rendered: string;
try {
rendered = evaluateTemplate(templateText, variables);
} catch (err) {
output({
rendered: false,
error: `Template rendering failed: ${err instanceof Error ? err.message : String(err)}`,
});
process.exit(1);
}
if (prepare) {
// Store in vault under prepared.<id>
const preparedKey = `prepared.${id}`;
writeValue(config, preparedKey, rendered);
// Store metadata
const meta: KeyMeta = {
template: false,
owner: 'system',
description: `Prepared prompt from template ${templateKey ?? templateFile}`,
};
saveKeyMeta(config, preparedKey, meta);
// Store schema if available
if (schema) {
const schemaKey = `prepared.${id}.schema`;
writeValue(config, schemaKey, JSON.stringify(schema, null, 2));
}
// Store template source reference
const refKey = `prepared.${id}.template_ref`;
writeValue(config, refKey, templateKey ?? templateFile ?? 'unknown');
output({
prepared: true,
id,
key: preparedKey,
substituted,
missing,
});
} else {
// Write to file
const outDir = require('node:path').dirname(outputFile!);
fs.mkdirSync(outDir, { recursive: true });
atomicWrite(outputFile!, rendered);
output({
rendered: true,
output_file: outputFile,
substituted,
missing,
});
}
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main().catch((err: unknown) => {
output({ rendered: false, error: String(err instanceof Error ? err.message : err) });
process.exit(1);
});
}