101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
|
|
import fs from 'node:fs/promises';
|
||
|
|
import os from 'node:os';
|
||
|
|
import path from 'node:path';
|
||
|
|
|
||
|
|
import { SkillsProvider } from '@/modules/providers/shared/skills/skills.provider.js';
|
||
|
|
import type { ProviderSkillSource } from '@/shared/types.js';
|
||
|
|
|
||
|
|
const hasGitMarker = async (dirPath: string): Promise<boolean> => {
|
||
|
|
try {
|
||
|
|
const gitMarkerStats = await fs.stat(path.join(dirPath, '.git'));
|
||
|
|
return gitMarkerStats.isDirectory() || gitMarkerStats.isFile();
|
||
|
|
} catch {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const findTopmostGitRoot = async (startPath: string): Promise<string | null> => {
|
||
|
|
let currentPath = path.resolve(startPath);
|
||
|
|
let topmostGitRoot: string | null = null;
|
||
|
|
|
||
|
|
while (true) {
|
||
|
|
if (await hasGitMarker(currentPath)) {
|
||
|
|
topmostGitRoot = currentPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
const parentPath = path.dirname(currentPath);
|
||
|
|
if (parentPath === currentPath) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
currentPath = parentPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
return topmostGitRoot;
|
||
|
|
};
|
||
|
|
|
||
|
|
const addUniqueSource = (
|
||
|
|
sources: ProviderSkillSource[],
|
||
|
|
seenRootDirs: Set<string>,
|
||
|
|
source: ProviderSkillSource,
|
||
|
|
): void => {
|
||
|
|
const normalizedRootDir = path.resolve(source.rootDir);
|
||
|
|
if (seenRootDirs.has(normalizedRootDir)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
seenRootDirs.add(normalizedRootDir);
|
||
|
|
sources.push({ ...source, rootDir: normalizedRootDir });
|
||
|
|
};
|
||
|
|
|
||
|
|
export class CodexSkillsProvider extends SkillsProvider {
|
||
|
|
constructor() {
|
||
|
|
super('codex');
|
||
|
|
}
|
||
|
|
|
||
|
|
protected async getSkillSources(workspacePath: string): Promise<ProviderSkillSource[]> {
|
||
|
|
const sources: ProviderSkillSource[] = [];
|
||
|
|
const seenRootDirs = new Set<string>();
|
||
|
|
const repoRoot = await findTopmostGitRoot(workspacePath);
|
||
|
|
|
||
|
|
addUniqueSource(sources, seenRootDirs, {
|
||
|
|
scope: 'repo',
|
||
|
|
rootDir: path.join(workspacePath, '.agents', 'skills'),
|
||
|
|
commandPrefix: '$',
|
||
|
|
});
|
||
|
|
|
||
|
|
if (repoRoot) {
|
||
|
|
// Codex checks repository skills at the launch folder, one folder above it,
|
||
|
|
// and the topmost git root; these can collapse to the same directory.
|
||
|
|
addUniqueSource(sources, seenRootDirs, {
|
||
|
|
scope: 'repo',
|
||
|
|
rootDir: path.join(path.dirname(workspacePath), '.agents', 'skills'),
|
||
|
|
commandPrefix: '$',
|
||
|
|
});
|
||
|
|
addUniqueSource(sources, seenRootDirs, {
|
||
|
|
scope: 'repo',
|
||
|
|
rootDir: path.join(repoRoot, '.agents', 'skills'),
|
||
|
|
commandPrefix: '$',
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
addUniqueSource(sources, seenRootDirs, {
|
||
|
|
scope: 'user',
|
||
|
|
rootDir: path.join(os.homedir(), '.agents', 'skills'),
|
||
|
|
commandPrefix: '$',
|
||
|
|
});
|
||
|
|
addUniqueSource(sources, seenRootDirs, {
|
||
|
|
scope: 'admin',
|
||
|
|
rootDir: path.join('/etc', 'codex', 'skills'),
|
||
|
|
commandPrefix: '$',
|
||
|
|
});
|
||
|
|
addUniqueSource(sources, seenRootDirs, {
|
||
|
|
scope: 'system',
|
||
|
|
rootDir: path.join(os.homedir(), '.codex', 'skills', '.system'),
|
||
|
|
commandPrefix: '$',
|
||
|
|
});
|
||
|
|
|
||
|
|
return sources;
|
||
|
|
}
|
||
|
|
}
|