mirror of
https://github.com/sweetwisdom/everything-claude-code-zh.git
synced 2026-03-21 22:10:09 +00:00
fix: restore missing files (package.json etc) and fix sync script logic
This commit is contained in:
67
scripts/ci/validate-agents.js
Normal file
67
scripts/ci/validate-agents.js
Normal file
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validate agent markdown files have required frontmatter
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const AGENTS_DIR = path.join(__dirname, '../../agents');
|
||||
const REQUIRED_FIELDS = ['model', 'tools'];
|
||||
|
||||
function extractFrontmatter(content) {
|
||||
// Strip BOM if present (UTF-8 BOM: \uFEFF)
|
||||
const cleanContent = content.replace(/^\uFEFF/, '');
|
||||
// Support both LF and CRLF line endings
|
||||
const match = cleanContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
||||
if (!match) return null;
|
||||
|
||||
const frontmatter = {};
|
||||
const lines = match[1].split('\n');
|
||||
for (const line of lines) {
|
||||
const colonIdx = line.indexOf(':');
|
||||
if (colonIdx > 0) {
|
||||
const key = line.slice(0, colonIdx).trim();
|
||||
const value = line.slice(colonIdx + 1).trim();
|
||||
frontmatter[key] = value;
|
||||
}
|
||||
}
|
||||
return frontmatter;
|
||||
}
|
||||
|
||||
function validateAgents() {
|
||||
if (!fs.existsSync(AGENTS_DIR)) {
|
||||
console.log('No agents directory found, skipping validation');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(AGENTS_DIR).filter(f => f.endsWith('.md'));
|
||||
let hasErrors = false;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(AGENTS_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const frontmatter = extractFrontmatter(content);
|
||||
|
||||
if (!frontmatter) {
|
||||
console.error(`ERROR: ${file} - Missing frontmatter`);
|
||||
hasErrors = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const field of REQUIRED_FIELDS) {
|
||||
if (!frontmatter[field]) {
|
||||
console.error(`ERROR: ${file} - Missing required field: ${field}`);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Validated ${files.length} agent files`);
|
||||
}
|
||||
|
||||
validateAgents();
|
||||
38
scripts/ci/validate-commands.js
Normal file
38
scripts/ci/validate-commands.js
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validate command markdown files are non-empty and readable
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const COMMANDS_DIR = path.join(__dirname, '../../commands');
|
||||
|
||||
function validateCommands() {
|
||||
if (!fs.existsSync(COMMANDS_DIR)) {
|
||||
console.log('No commands directory found, skipping validation');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(COMMANDS_DIR).filter(f => f.endsWith('.md'));
|
||||
let hasErrors = false;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(COMMANDS_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
// Validate the file is non-empty readable markdown
|
||||
if (content.trim().length === 0) {
|
||||
console.error(`ERROR: ${file} - Empty command file`);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Validated ${files.length} command files`);
|
||||
}
|
||||
|
||||
validateCommands();
|
||||
116
scripts/ci/validate-hooks.js
Normal file
116
scripts/ci/validate-hooks.js
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validate hooks.json schema
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const HOOKS_FILE = path.join(__dirname, '../../hooks/hooks.json');
|
||||
const VALID_EVENTS = ['PreToolUse', 'PostToolUse', 'PreCompact', 'SessionStart', 'SessionEnd', 'Stop', 'Notification', 'SubagentStop'];
|
||||
|
||||
function validateHooks() {
|
||||
if (!fs.existsSync(HOOKS_FILE)) {
|
||||
console.log('No hooks.json found, skipping validation');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(fs.readFileSync(HOOKS_FILE, 'utf-8'));
|
||||
} catch (e) {
|
||||
console.error(`ERROR: Invalid JSON in hooks.json: ${e.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Support both object format { hooks: {...} } and array format
|
||||
const hooks = data.hooks || data;
|
||||
let hasErrors = false;
|
||||
let totalMatchers = 0;
|
||||
|
||||
if (typeof hooks === 'object' && !Array.isArray(hooks)) {
|
||||
// Object format: { EventType: [matchers] }
|
||||
for (const [eventType, matchers] of Object.entries(hooks)) {
|
||||
if (!VALID_EVENTS.includes(eventType)) {
|
||||
console.error(`ERROR: Invalid event type: ${eventType}`);
|
||||
hasErrors = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Array.isArray(matchers)) {
|
||||
console.error(`ERROR: ${eventType} must be an array`);
|
||||
hasErrors = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < matchers.length; i++) {
|
||||
const matcher = matchers[i];
|
||||
if (typeof matcher !== 'object' || matcher === null) {
|
||||
console.error(`ERROR: ${eventType}[${i}] is not an object`);
|
||||
hasErrors = true;
|
||||
continue;
|
||||
}
|
||||
if (!matcher.matcher) {
|
||||
console.error(`ERROR: ${eventType}[${i}] missing 'matcher' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
if (!matcher.hooks || !Array.isArray(matcher.hooks)) {
|
||||
console.error(`ERROR: ${eventType}[${i}] missing 'hooks' array`);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
// Validate each hook entry
|
||||
for (let j = 0; j < matcher.hooks.length; j++) {
|
||||
const hook = matcher.hooks[j];
|
||||
if (!hook.type || typeof hook.type !== 'string') {
|
||||
console.error(`ERROR: ${eventType}[${i}].hooks[${j}] missing or invalid 'type' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
if (!hook.command || (typeof hook.command !== 'string' && !Array.isArray(hook.command))) {
|
||||
console.error(`ERROR: ${eventType}[${i}].hooks[${j}] missing or invalid 'command' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
totalMatchers++;
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(hooks)) {
|
||||
// Array format (legacy)
|
||||
for (let i = 0; i < hooks.length; i++) {
|
||||
const hook = hooks[i];
|
||||
if (!hook.matcher) {
|
||||
console.error(`ERROR: Hook ${i} missing 'matcher' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
if (!hook.hooks || !Array.isArray(hook.hooks)) {
|
||||
console.error(`ERROR: Hook ${i} missing 'hooks' array`);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
// Validate each hook entry
|
||||
for (let j = 0; j < hook.hooks.length; j++) {
|
||||
const h = hook.hooks[j];
|
||||
if (!h.type || typeof h.type !== 'string') {
|
||||
console.error(`ERROR: Hook ${i}.hooks[${j}] missing or invalid 'type' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
if (!h.command || (typeof h.command !== 'string' && !Array.isArray(h.command))) {
|
||||
console.error(`ERROR: Hook ${i}.hooks[${j}] missing or invalid 'command' field`);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
totalMatchers++;
|
||||
}
|
||||
} else {
|
||||
console.error('ERROR: hooks.json must be an object or array');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Validated ${totalMatchers} hook matchers`);
|
||||
}
|
||||
|
||||
validateHooks();
|
||||
48
scripts/ci/validate-rules.js
Normal file
48
scripts/ci/validate-rules.js
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validate rule markdown files
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const RULES_DIR = path.join(__dirname, '../../rules');
|
||||
|
||||
function validateRules() {
|
||||
if (!fs.existsSync(RULES_DIR)) {
|
||||
console.log('No rules directory found, skipping validation');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(RULES_DIR, { recursive: true })
|
||||
.filter(f => f.endsWith('.md'));
|
||||
let hasErrors = false;
|
||||
let validatedCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(RULES_DIR, file);
|
||||
try {
|
||||
const stat = fs.statSync(filePath);
|
||||
if (!stat.isFile()) continue;
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
if (content.trim().length === 0) {
|
||||
console.error(`ERROR: ${file} - Empty rule file`);
|
||||
hasErrors = true;
|
||||
continue;
|
||||
}
|
||||
validatedCount++;
|
||||
} catch (err) {
|
||||
console.error(`ERROR: ${file} - ${err.message}`);
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Validated ${validatedCount} rule files`);
|
||||
}
|
||||
|
||||
validateRules();
|
||||
47
scripts/ci/validate-skills.js
Normal file
47
scripts/ci/validate-skills.js
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validate skill directories have SKILL.md with required structure
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SKILLS_DIR = path.join(__dirname, '../../skills');
|
||||
|
||||
function validateSkills() {
|
||||
if (!fs.existsSync(SKILLS_DIR)) {
|
||||
console.log('No skills directory found, skipping validation');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const entries = fs.readdirSync(SKILLS_DIR, { withFileTypes: true });
|
||||
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
||||
let hasErrors = false;
|
||||
let validCount = 0;
|
||||
|
||||
for (const dir of dirs) {
|
||||
const skillMd = path.join(SKILLS_DIR, dir, 'SKILL.md');
|
||||
if (!fs.existsSync(skillMd)) {
|
||||
console.error(`ERROR: ${dir}/ - Missing SKILL.md`);
|
||||
hasErrors = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(skillMd, 'utf-8');
|
||||
if (content.trim().length === 0) {
|
||||
console.error(`ERROR: ${dir}/SKILL.md - Empty file`);
|
||||
hasErrors = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
validCount++;
|
||||
}
|
||||
|
||||
if (hasErrors) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Validated ${validCount} skill directories`);
|
||||
}
|
||||
|
||||
validateSkills();
|
||||
244
scripts/skill-create-output.js
Normal file
244
scripts/skill-create-output.js
Normal file
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Skill Creator - Pretty Output Formatter
|
||||
*
|
||||
* Creates beautiful terminal output for the /skill-create command
|
||||
* similar to @mvanhorn's /last30days skill
|
||||
*/
|
||||
|
||||
// ANSI color codes - no external dependencies
|
||||
const chalk = {
|
||||
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
||||
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
||||
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
||||
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
||||
magenta: (s) => `\x1b[35m${s}\x1b[0m`,
|
||||
gray: (s) => `\x1b[90m${s}\x1b[0m`,
|
||||
white: (s) => `\x1b[37m${s}\x1b[0m`,
|
||||
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
||||
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
||||
bgCyan: (s) => `\x1b[46m${s}\x1b[0m`,
|
||||
};
|
||||
|
||||
// Box drawing characters
|
||||
const BOX = {
|
||||
topLeft: '╭',
|
||||
topRight: '╮',
|
||||
bottomLeft: '╰',
|
||||
bottomRight: '╯',
|
||||
horizontal: '─',
|
||||
vertical: '│',
|
||||
verticalRight: '├',
|
||||
verticalLeft: '┤',
|
||||
};
|
||||
|
||||
// Progress spinner frames
|
||||
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||
|
||||
// Helper functions
|
||||
function box(title, content, width = 60) {
|
||||
const lines = content.split('\n');
|
||||
const top = `${BOX.topLeft}${BOX.horizontal} ${chalk.bold(chalk.cyan(title))} ${BOX.horizontal.repeat(width - title.length - 5)}${BOX.topRight}`;
|
||||
const bottom = `${BOX.bottomLeft}${BOX.horizontal.repeat(width - 1)}${BOX.bottomRight}`;
|
||||
const middle = lines.map(line => {
|
||||
const padding = width - 3 - stripAnsi(line).length;
|
||||
return `${BOX.vertical} ${line}${' '.repeat(Math.max(0, padding))} ${BOX.vertical}`;
|
||||
}).join('\n');
|
||||
return `${top}\n${middle}\n${bottom}`;
|
||||
}
|
||||
|
||||
function stripAnsi(str) {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
}
|
||||
|
||||
function progressBar(percent, width = 30) {
|
||||
const filled = Math.round(width * percent / 100);
|
||||
const empty = width - filled;
|
||||
const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
||||
return `${bar} ${chalk.bold(percent)}%`;
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function animateProgress(label, steps, callback) {
|
||||
process.stdout.write(`\n${chalk.cyan('⏳')} ${label}...\n`);
|
||||
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
const step = steps[i];
|
||||
process.stdout.write(` ${chalk.gray(SPINNER[i % SPINNER.length])} ${step.name}`);
|
||||
await sleep(step.duration || 500);
|
||||
process.stdout.clearLine?.(0) || process.stdout.write('\r');
|
||||
process.stdout.cursorTo?.(0) || process.stdout.write('\r');
|
||||
process.stdout.write(` ${chalk.green('✓')} ${step.name}\n`);
|
||||
if (callback) callback(step, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Main output formatter
|
||||
class SkillCreateOutput {
|
||||
constructor(repoName, options = {}) {
|
||||
this.repoName = repoName;
|
||||
this.options = options;
|
||||
this.width = options.width || 70;
|
||||
}
|
||||
|
||||
header() {
|
||||
const subtitle = `Extracting patterns from ${chalk.cyan(this.repoName)}`;
|
||||
|
||||
console.log('\n');
|
||||
console.log(chalk.bold(chalk.magenta('╔════════════════════════════════════════════════════════════════╗')));
|
||||
console.log(chalk.bold(chalk.magenta('║')) + chalk.bold(' 🔮 ECC Skill Creator ') + chalk.bold(chalk.magenta('║')));
|
||||
console.log(chalk.bold(chalk.magenta('║')) + ` ${subtitle}${' '.repeat(Math.max(0, 55 - stripAnsi(subtitle).length))}` + chalk.bold(chalk.magenta('║')));
|
||||
console.log(chalk.bold(chalk.magenta('╚════════════════════════════════════════════════════════════════╝')));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
async analyzePhase(data) {
|
||||
const steps = [
|
||||
{ name: 'Parsing git history...', duration: 300 },
|
||||
{ name: `Found ${chalk.yellow(data.commits)} commits`, duration: 200 },
|
||||
{ name: 'Analyzing commit patterns...', duration: 400 },
|
||||
{ name: 'Detecting file co-changes...', duration: 300 },
|
||||
{ name: 'Identifying workflows...', duration: 400 },
|
||||
{ name: 'Extracting architecture patterns...', duration: 300 },
|
||||
];
|
||||
|
||||
await animateProgress('Analyzing Repository', steps);
|
||||
}
|
||||
|
||||
analysisResults(data) {
|
||||
console.log('\n');
|
||||
console.log(box('📊 Analysis Results', `
|
||||
${chalk.bold('Commits Analyzed:')} ${chalk.yellow(data.commits)}
|
||||
${chalk.bold('Time Range:')} ${chalk.gray(data.timeRange)}
|
||||
${chalk.bold('Contributors:')} ${chalk.cyan(data.contributors)}
|
||||
${chalk.bold('Files Tracked:')} ${chalk.green(data.files)}
|
||||
`));
|
||||
}
|
||||
|
||||
patterns(patterns) {
|
||||
console.log('\n');
|
||||
console.log(chalk.bold(chalk.cyan('🔍 Key Patterns Discovered:')));
|
||||
console.log(chalk.gray('─'.repeat(50)));
|
||||
|
||||
patterns.forEach((pattern, i) => {
|
||||
const confidence = pattern.confidence || 0.8;
|
||||
const confidenceBar = progressBar(Math.round(confidence * 100), 15);
|
||||
console.log(`
|
||||
${chalk.bold(chalk.yellow(`${i + 1}.`))} ${chalk.bold(pattern.name)}
|
||||
${chalk.gray('Trigger:')} ${pattern.trigger}
|
||||
${chalk.gray('Confidence:')} ${confidenceBar}
|
||||
${chalk.dim(pattern.evidence)}`);
|
||||
});
|
||||
}
|
||||
|
||||
instincts(instincts) {
|
||||
console.log('\n');
|
||||
console.log(box('🧠 Instincts Generated', instincts.map((inst, i) =>
|
||||
`${chalk.yellow(`${i + 1}.`)} ${chalk.bold(inst.name)} ${chalk.gray(`(${Math.round(inst.confidence * 100)}%)`)}`
|
||||
).join('\n')));
|
||||
}
|
||||
|
||||
output(skillPath, instinctsPath) {
|
||||
console.log('\n');
|
||||
console.log(chalk.bold(chalk.green('✨ Generation Complete!')));
|
||||
console.log(chalk.gray('─'.repeat(50)));
|
||||
console.log(`
|
||||
${chalk.green('📄')} ${chalk.bold('Skill File:')}
|
||||
${chalk.cyan(skillPath)}
|
||||
|
||||
${chalk.green('🧠')} ${chalk.bold('Instincts File:')}
|
||||
${chalk.cyan(instinctsPath)}
|
||||
`);
|
||||
}
|
||||
|
||||
nextSteps() {
|
||||
console.log(box('📋 Next Steps', `
|
||||
${chalk.yellow('1.')} Review the generated SKILL.md
|
||||
${chalk.yellow('2.')} Import instincts: ${chalk.cyan('/instinct-import <path>')}
|
||||
${chalk.yellow('3.')} View learned patterns: ${chalk.cyan('/instinct-status')}
|
||||
${chalk.yellow('4.')} Evolve into skills: ${chalk.cyan('/evolve')}
|
||||
`));
|
||||
console.log('\n');
|
||||
}
|
||||
|
||||
footer() {
|
||||
console.log(chalk.gray('─'.repeat(60)));
|
||||
console.log(chalk.dim(` Powered by Everything Claude Code • ecc.tools`));
|
||||
console.log(chalk.dim(` GitHub App: github.com/apps/skill-creator`));
|
||||
console.log('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Demo function to show the output
|
||||
async function demo() {
|
||||
const output = new SkillCreateOutput('PMX');
|
||||
|
||||
output.header();
|
||||
|
||||
await output.analyzePhase({
|
||||
commits: 200,
|
||||
});
|
||||
|
||||
output.analysisResults({
|
||||
commits: 200,
|
||||
timeRange: 'Nov 2024 - Jan 2025',
|
||||
contributors: 4,
|
||||
files: 847,
|
||||
});
|
||||
|
||||
output.patterns([
|
||||
{
|
||||
name: 'Conventional Commits',
|
||||
trigger: 'when writing commit messages',
|
||||
confidence: 0.85,
|
||||
evidence: 'Found in 150/200 commits (feat:, fix:, refactor:)',
|
||||
},
|
||||
{
|
||||
name: 'Client/Server Component Split',
|
||||
trigger: 'when creating Next.js pages',
|
||||
confidence: 0.90,
|
||||
evidence: 'Observed in markets/, premarkets/, portfolio/',
|
||||
},
|
||||
{
|
||||
name: 'Service Layer Architecture',
|
||||
trigger: 'when adding backend logic',
|
||||
confidence: 0.85,
|
||||
evidence: 'Business logic in services/, not routes/',
|
||||
},
|
||||
{
|
||||
name: 'TDD with E2E Tests',
|
||||
trigger: 'when adding features',
|
||||
confidence: 0.75,
|
||||
evidence: '9 E2E test files, test(e2e) commits common',
|
||||
},
|
||||
]);
|
||||
|
||||
output.instincts([
|
||||
{ name: 'pmx-conventional-commits', confidence: 0.85 },
|
||||
{ name: 'pmx-client-component-pattern', confidence: 0.90 },
|
||||
{ name: 'pmx-service-layer', confidence: 0.85 },
|
||||
{ name: 'pmx-e2e-test-location', confidence: 0.90 },
|
||||
{ name: 'pmx-package-manager', confidence: 0.95 },
|
||||
{ name: 'pmx-hot-path-caution', confidence: 0.90 },
|
||||
]);
|
||||
|
||||
output.output(
|
||||
'.claude/skills/pmx-patterns/SKILL.md',
|
||||
'.claude/homunculus/instincts/inherited/pmx-instincts.yaml'
|
||||
);
|
||||
|
||||
output.nextSteps();
|
||||
output.footer();
|
||||
}
|
||||
|
||||
// Export for use in other scripts
|
||||
module.exports = { SkillCreateOutput, demo };
|
||||
|
||||
// Run demo if executed directly
|
||||
if (require.main === module) {
|
||||
demo().catch(console.error);
|
||||
}
|
||||
Reference in New Issue
Block a user