116 lines
3.2 KiB
TypeScript
116 lines
3.2 KiB
TypeScript
import {
|
|
cpSync,
|
|
existsSync,
|
|
lstatSync,
|
|
mkdirSync,
|
|
rmSync,
|
|
unlinkSync,
|
|
} from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
export interface BundledSkillsPathInput {
|
|
isPackaged: boolean;
|
|
resourcesPath: string;
|
|
appPath: string;
|
|
}
|
|
|
|
export interface EnsureBundledCourseSkillsOptions {
|
|
managedConfigDir: string;
|
|
sourceDir?: string;
|
|
}
|
|
|
|
export const BUNDLED_COURSE_SKILL_IDS = [
|
|
'agent-browser',
|
|
'designer-design-spec',
|
|
'dev-build-test',
|
|
'game-assets',
|
|
'marketing-launch-story',
|
|
'nianxxgame-skill',
|
|
'partner-agent-showcase',
|
|
'pm-project-plan',
|
|
'product-demo-prototype',
|
|
'ui-ux-course-quality',
|
|
'youth-plain-language',
|
|
'youth-ai-product-course',
|
|
] as const;
|
|
const RETIRED_COURSE_SKILL_IDS = [
|
|
'course-stage-review',
|
|
'deploy-publish-check',
|
|
'student-growth-logger',
|
|
] as const;
|
|
const LEGACY_SUPERPOWERS_ARTIFACTS = [
|
|
'plugins/superpowers-niancode.js',
|
|
'plugins/superpowers.js',
|
|
'superpowers-active.json',
|
|
'superpowers-bundles',
|
|
] as const;
|
|
|
|
export function resolveBundledCourseSkillsDir(input: BundledSkillsPathInput): string {
|
|
return input.isPackaged
|
|
? join(input.resourcesPath, 'course-skills')
|
|
: join(input.appPath, '.opencode', 'skills');
|
|
}
|
|
|
|
export function resolveBundledAgentBrowserPluginPath(input: BundledSkillsPathInput): string {
|
|
return join(
|
|
resolveBundledCourseSkillsDir(input),
|
|
'agent-browser',
|
|
'.opencode',
|
|
'plugins',
|
|
'niancode-agent-browser.js',
|
|
);
|
|
}
|
|
|
|
export function getManagedOpencodeConfigDir(userDataDir: string): string {
|
|
return join(userDataDir, 'opencode', 'niancode-config');
|
|
}
|
|
|
|
function copyDirectorySync(sourceDir: string, targetDir: string): void {
|
|
cpSync(sourceDir, targetDir, {
|
|
recursive: true,
|
|
force: true,
|
|
filter: (sourcePath, targetPath) => {
|
|
// Node 24.14's Windows override path can corrupt non-ASCII destinations
|
|
// when replacing a file. Public unlink keeps force-overwrite semantics.
|
|
if (lstatSync(sourcePath).isFile() && existsSync(targetPath)) {
|
|
unlinkSync(targetPath);
|
|
}
|
|
return true;
|
|
},
|
|
});
|
|
}
|
|
|
|
export function removeLegacySuperpowersArtifacts(managedConfigDir: string): void {
|
|
for (const artifact of LEGACY_SUPERPOWERS_ARTIFACTS) {
|
|
rmSync(join(managedConfigDir, artifact), { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
export function ensureBundledCourseSkills(options: EnsureBundledCourseSkillsOptions): boolean {
|
|
const sourceDir = options.sourceDir?.trim();
|
|
if (!sourceDir || !existsSync(sourceDir)) {
|
|
return false;
|
|
}
|
|
|
|
const targetSkillsDir = join(options.managedConfigDir, 'skills');
|
|
mkdirSync(targetSkillsDir, { recursive: true });
|
|
for (const skillId of RETIRED_COURSE_SKILL_IDS) {
|
|
rmSync(join(targetSkillsDir, skillId), { recursive: true, force: true });
|
|
}
|
|
copyDirectorySync(sourceDir, targetSkillsDir);
|
|
|
|
return true;
|
|
}
|
|
|
|
export function ensureBundledAgentBrowserPlugin(options: {
|
|
managedConfigDir: string;
|
|
sourcePath?: string;
|
|
}): boolean {
|
|
const sourcePath = options.sourcePath?.trim();
|
|
if (!sourcePath || !existsSync(sourcePath)) return false;
|
|
const targetPluginsDir = join(options.managedConfigDir, 'plugins');
|
|
mkdirSync(targetPluginsDir, { recursive: true });
|
|
cpSync(sourcePath, join(targetPluginsDir, 'niancode-agent-browser.js'), { force: true });
|
|
return true;
|
|
}
|