Two distribution defects left installed skills stale (issue PeterGuy326#8): 1. skill setup resolved its source from cwd / exe-adjacent checkouts, so an upgraded binary happily re-installed whatever stale checkout it was run from, and agents kept routing 'create robot' into the chat dead end. The skills/mono and skills/multi trees are now embedded (go:embed) and setup defaults to that copy, materialized into ~/.dws/skills/<mode> — upgrading the binary upgrades what setup installs. --source and DWS_SKILL_SOURCE stay as explicit overrides, and an override that has no skill root errors out instead of silently falling back. 2. dws upgrade's skill refresh (knownSkillDirs) skipped ~/.qoder and ~/.qoderwork, so Qoder/QoderWork hosts never got refreshed routing. Added both to the dir table and its seven mirrors (npm install.js, install.sh/ps1, install-skills.sh, homebrew template, package test, verify-package-managers.sh). setup-side detection and --target qoder/qoderwork already work on this branch; this closes the remaining upgrade-side and source-side gaps.
199 lines
6.2 KiB
JavaScript
Executable File
199 lines
6.2 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
"use strict";
|
|
|
|
const fs = require("fs");
|
|
const os = require("os");
|
|
const path = require("path");
|
|
const childProcess = require("child_process");
|
|
|
|
// Canonical list: keep scripts/install.sh, scripts/install.ps1, scripts/install-skills.sh in sync.
|
|
const AGENT_DIRS = [
|
|
".agents/skills",
|
|
".claude/skills",
|
|
".cursor/skills",
|
|
".qoder/skills",
|
|
".qoderwork/skills",
|
|
".gemini/skills",
|
|
".codex/skills",
|
|
".github/skills",
|
|
".windsurf/skills",
|
|
".augment/skills",
|
|
".cline/skills",
|
|
".amp/skills",
|
|
".kiro/skills",
|
|
".trae/skills",
|
|
".openclaw/skills",
|
|
".hermes/skills",
|
|
];
|
|
|
|
const PLATFORM_MAP = {
|
|
"darwin-x64": "dws-darwin-amd64.tar.gz",
|
|
"darwin-arm64": "dws-darwin-arm64.tar.gz",
|
|
"linux-x64": "dws-linux-amd64.tar.gz",
|
|
"linux-arm64": "dws-linux-arm64.tar.gz",
|
|
"win32-x64": "dws-windows-amd64.zip",
|
|
"win32-arm64": "dws-windows-arm64.zip",
|
|
};
|
|
|
|
function run(command, args) {
|
|
childProcess.execFileSync(command, args, { stdio: "inherit" });
|
|
}
|
|
|
|
function ensureCleanDir(dir) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
}
|
|
|
|
function findBinary(root) {
|
|
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const entryPath = path.join(root, entry.name);
|
|
if (entry.isDirectory()) {
|
|
const nested = findBinary(entryPath);
|
|
if (nested) {
|
|
return nested;
|
|
}
|
|
continue;
|
|
}
|
|
if (entry.name === "dws" || entry.name === "dws.exe") {
|
|
return entryPath;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function extractArchive(archivePath, destDir) {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "dws-npm-bin-"));
|
|
try {
|
|
if (archivePath.endsWith(".tar.gz")) {
|
|
run("tar", ["-xzf", archivePath, "-C", tmpDir]);
|
|
} else if (process.platform === "win32") {
|
|
run("powershell.exe", [
|
|
"-NoLogo",
|
|
"-NoProfile",
|
|
"-Command",
|
|
`Expand-Archive -Path '${archivePath.replace(/'/g, "''")}' -DestinationPath '${tmpDir.replace(/'/g, "''")}' -Force`,
|
|
]);
|
|
} else {
|
|
run("unzip", ["-q", archivePath, "-d", tmpDir]);
|
|
}
|
|
|
|
const binaryPath = findBinary(tmpDir);
|
|
if (!binaryPath) {
|
|
throw new Error(`dws binary not found in archive ${archivePath}`);
|
|
}
|
|
|
|
ensureCleanDir(destDir);
|
|
const targetName = process.platform === "win32" ? "dws.exe" : "dws";
|
|
const targetPath = path.join(destDir, targetName);
|
|
fs.copyFileSync(binaryPath, targetPath);
|
|
if (process.platform !== "win32") {
|
|
fs.chmodSync(targetPath, 0o755);
|
|
}
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function extractSkills(zipPath, destDir) {
|
|
ensureCleanDir(destDir);
|
|
if (process.platform === "win32") {
|
|
run("powershell.exe", [
|
|
"-NoLogo",
|
|
"-NoProfile",
|
|
"-Command",
|
|
`Expand-Archive -Path '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force`,
|
|
]);
|
|
return;
|
|
}
|
|
run("unzip", ["-q", zipPath, "-d", destDir]);
|
|
}
|
|
|
|
function copyChildren(srcDir, destDir) {
|
|
fs.mkdirSync(destDir, { recursive: true });
|
|
for (const entry of fs.readdirSync(srcDir)) {
|
|
fs.cpSync(path.join(srcDir, entry), path.join(destDir, entry), { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function installSkillsToHomes(skillRoot) {
|
|
const homeDir = os.homedir();
|
|
let installed = 0;
|
|
|
|
AGENT_DIRS.forEach((agentDir, index) => {
|
|
const baseDir = path.join(homeDir, agentDir);
|
|
const parentGate = path.dirname(baseDir);
|
|
if (index > 0 && !fs.existsSync(parentGate)) {
|
|
return;
|
|
}
|
|
const destDir = path.join(baseDir, "dws");
|
|
fs.rmSync(destDir, { recursive: true, force: true });
|
|
copyChildren(skillRoot, destDir);
|
|
installed += 1;
|
|
});
|
|
|
|
if (installed === 0) {
|
|
copyChildren(skillRoot, path.join(homeDir, ".agents", "skills", "dws"));
|
|
}
|
|
}
|
|
|
|
// cacheUserSkills copies the mono and multi trees out of the freshly extracted
|
|
// dws-skills.zip into ~/.dws/skills/{mono,multi}/ so that `dws skill setup`
|
|
// can fall back to a user-local cache when --source is not provided. mono is
|
|
// already installed into agent homes by installSkillsToHomes; the cache is
|
|
// purely a source-of-truth for the setup command.
|
|
function cacheUserSkills(extractedSkillsRoot) {
|
|
const cacheBase = path.join(os.homedir(), ".dws", "skills");
|
|
|
|
const monoSource = fs.existsSync(path.join(extractedSkillsRoot, "mono", "SKILL.md"))
|
|
? path.join(extractedSkillsRoot, "mono")
|
|
: extractedSkillsRoot;
|
|
const monoCache = path.join(cacheBase, "mono");
|
|
fs.rmSync(monoCache, { recursive: true, force: true });
|
|
copyChildren(monoSource, monoCache);
|
|
|
|
const multiSource = path.join(extractedSkillsRoot, "multi");
|
|
if (fs.existsSync(multiSource) && fs.statSync(multiSource).isDirectory()) {
|
|
const multiCache = path.join(cacheBase, "multi");
|
|
fs.rmSync(multiCache, { recursive: true, force: true });
|
|
copyChildren(multiSource, multiCache);
|
|
}
|
|
}
|
|
|
|
function main() {
|
|
const packageRoot = __dirname;
|
|
const assetsDir = path.join(packageRoot, "assets");
|
|
const vendorDir = path.join(packageRoot, "vendor");
|
|
// Extract dws-skills.zip into a staging directory so we can split mono/
|
|
// (installed to agent homes) from multi/ (cached for later setup use).
|
|
const skillsStaging = path.join(packageRoot, "share", "skills");
|
|
const assetName = PLATFORM_MAP[`${process.platform}-${process.arch}`];
|
|
if (!assetName) {
|
|
throw new Error(`unsupported platform: ${process.platform}/${process.arch}`);
|
|
}
|
|
|
|
const archivePath = path.join(assetsDir, assetName);
|
|
const skillsPath = path.join(assetsDir, "dws-skills.zip");
|
|
if (!fs.existsSync(archivePath)) {
|
|
throw new Error(`missing platform archive: ${archivePath}`);
|
|
}
|
|
if (!fs.existsSync(skillsPath)) {
|
|
throw new Error(`missing skills archive: ${skillsPath}`);
|
|
}
|
|
|
|
extractArchive(archivePath, vendorDir);
|
|
extractSkills(skillsPath, skillsStaging);
|
|
|
|
// For backward compatibility, the zip root carries a copy of mono content
|
|
// (SKILL.md + references/ + scripts/). Prefer the explicit mono/ subdir
|
|
// when present; fall back to the staging root otherwise.
|
|
const monoRoot = fs.existsSync(path.join(skillsStaging, "mono", "SKILL.md"))
|
|
? path.join(skillsStaging, "mono")
|
|
: skillsStaging;
|
|
installSkillsToHomes(monoRoot);
|
|
cacheUserSkills(skillsStaging);
|
|
}
|
|
|
|
main();
|