116 lines
3.1 KiB
JavaScript
116 lines
3.1 KiB
JavaScript
// Shared CLI skill entrypoint bootstrap — used by npx init and update-system.
|
|
// Ensures every supported CLI gets .*/skills/career-ops/SKILL.md even when the
|
|
// cloned release predates a CLI (e.g. Grok on v1.13.0). Materializes pointer
|
|
// files to canonical content on filesystems without symlink support.
|
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, lstatSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
|
|
export const CANONICAL_SKILL_PATH = '.agents/skills/career-ops/SKILL.md';
|
|
|
|
export const SKILL_ENTRYPOINTS = [
|
|
{
|
|
path: '.claude/skills/career-ops/SKILL.md',
|
|
pointer: '../../../.agents/skills/career-ops/SKILL.md',
|
|
},
|
|
{
|
|
path: '.opencode/skills/career-ops/SKILL.md',
|
|
pointer: '../../../.agents/skills/career-ops/SKILL.md',
|
|
},
|
|
{
|
|
path: '.qwen/skills/career-ops/SKILL.md',
|
|
pointer: '../../../.agents/skills/career-ops/SKILL.md',
|
|
},
|
|
{
|
|
path: '.antigravitycli/skills/career-ops/SKILL.md',
|
|
pointer: '../../../.agents/skills/career-ops/SKILL.md',
|
|
},
|
|
{
|
|
path: '.grok/skills/career-ops/SKILL.md',
|
|
pointer: '../../../.agents/skills/career-ops/SKILL.md',
|
|
},
|
|
];
|
|
|
|
function repoPath(root, path) {
|
|
return join(root, ...path.split('/'));
|
|
}
|
|
|
|
function readCanonical(root) {
|
|
const canonicalPath = repoPath(root, CANONICAL_SKILL_PATH);
|
|
if (!existsSync(canonicalPath)) return null;
|
|
try {
|
|
return readFileSync(canonicalPath, 'utf-8');
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function materializeSkillEntrypoints(root) {
|
|
const canonicalContent = readCanonical(root);
|
|
if (canonicalContent === null) return [];
|
|
|
|
const materialized = [];
|
|
for (const entry of SKILL_ENTRYPOINTS) {
|
|
const entryPath = repoPath(root, entry.path);
|
|
if (!existsSync(entryPath)) continue;
|
|
|
|
let stat = null;
|
|
try {
|
|
stat = lstatSync(entryPath);
|
|
} catch {
|
|
continue;
|
|
}
|
|
if (stat.isSymbolicLink()) continue;
|
|
if (!stat.isFile()) continue;
|
|
|
|
try {
|
|
const content = readFileSync(entryPath, 'utf-8').trim();
|
|
if (content !== entry.pointer) continue;
|
|
writeFileSync(entryPath, canonicalContent);
|
|
} catch {
|
|
continue;
|
|
}
|
|
materialized.push(entry.path);
|
|
}
|
|
|
|
return materialized;
|
|
}
|
|
|
|
export function ensureSkillEntrypoints(root) {
|
|
const canonicalContent = readCanonical(root);
|
|
if (canonicalContent === null) return [];
|
|
|
|
const touched = [];
|
|
for (const entry of SKILL_ENTRYPOINTS) {
|
|
const entryPath = repoPath(root, entry.path);
|
|
|
|
if (!existsSync(entryPath)) {
|
|
try {
|
|
mkdirSync(dirname(entryPath), { recursive: true });
|
|
writeFileSync(entryPath, entry.pointer);
|
|
touched.push(entry.path);
|
|
} catch {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
let stat = null;
|
|
try {
|
|
stat = lstatSync(entryPath);
|
|
} catch {
|
|
continue;
|
|
}
|
|
if (stat.isSymbolicLink()) continue;
|
|
if (!stat.isFile()) continue;
|
|
|
|
try {
|
|
const content = readFileSync(entryPath, 'utf-8').trim();
|
|
if (content !== entry.pointer) continue;
|
|
writeFileSync(entryPath, canonicalContent);
|
|
if (!touched.includes(entry.path)) touched.push(entry.path);
|
|
} catch {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return touched;
|
|
} |