reskill 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -255
- package/README.zh-CN.md +46 -264
- package/dist/cli/commands/__integration__/helpers.d.ts +131 -0
- package/dist/cli/commands/__integration__/helpers.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +119 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +15 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/index.js +633 -26
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/git-resolver.d.ts +1 -2
- package/dist/core/git-resolver.d.ts.map +1 -1
- package/dist/core/skill-manager.d.ts.map +1 -1
- package/dist/index.js +2 -9
- package/dist/types/index.d.ts +1 -9
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -831,12 +831,10 @@ class CacheManager {
|
|
|
831
831
|
const DEFAULT_SKILLS_JSON = {
|
|
832
832
|
skills: {},
|
|
833
833
|
defaults: {
|
|
834
|
-
registry: 'github',
|
|
835
834
|
installDir: '.skills'
|
|
836
835
|
}
|
|
837
836
|
};
|
|
838
837
|
const DEFAULT_VALUES = {
|
|
839
|
-
registry: 'github',
|
|
840
838
|
installDir: '.skills',
|
|
841
839
|
targetAgents: [],
|
|
842
840
|
installMode: 'symlink'
|
|
@@ -908,7 +906,6 @@ class ConfigLoader {
|
|
|
908
906
|
const config = this.getConfigOrDefault();
|
|
909
907
|
const storedDefaults = config.defaults ?? {};
|
|
910
908
|
return {
|
|
911
|
-
registry: storedDefaults.registry ?? DEFAULT_VALUES.registry,
|
|
912
909
|
installDir: storedDefaults.installDir ?? DEFAULT_VALUES.installDir,
|
|
913
910
|
targetAgents: storedDefaults.targetAgents ?? DEFAULT_VALUES.targetAgents,
|
|
914
911
|
installMode: storedDefaults.installMode ?? DEFAULT_VALUES.installMode
|
|
@@ -973,10 +970,7 @@ class ConfigLoader {
|
|
|
973
970
|
}
|
|
974
971
|
}
|
|
975
972
|
class GitResolver {
|
|
976
|
-
defaultRegistry;
|
|
977
|
-
constructor(defaultRegistry = 'github'){
|
|
978
|
-
this.defaultRegistry = defaultRegistry;
|
|
979
|
-
}
|
|
973
|
+
defaultRegistry = 'github';
|
|
980
974
|
parseRef(ref) {
|
|
981
975
|
const raw = ref;
|
|
982
976
|
if (isGitUrl(ref)) return this.parseGitUrlRef(ref);
|
|
@@ -1274,8 +1268,7 @@ class SkillManager {
|
|
|
1274
1268
|
this.config = new ConfigLoader(this.projectRoot);
|
|
1275
1269
|
this.lockManager = new LockManager(this.projectRoot);
|
|
1276
1270
|
this.cache = new CacheManager();
|
|
1277
|
-
|
|
1278
|
-
this.resolver = new GitResolver(defaults.registry);
|
|
1271
|
+
this.resolver = new GitResolver();
|
|
1279
1272
|
}
|
|
1280
1273
|
isGlobalMode() {
|
|
1281
1274
|
return this.isGlobal;
|
|
@@ -1801,6 +1794,619 @@ function maybeHandleCompletion() {
|
|
|
1801
1794
|
}
|
|
1802
1795
|
return false;
|
|
1803
1796
|
}
|
|
1797
|
+
function getStatusIcon(status) {
|
|
1798
|
+
switch(status){
|
|
1799
|
+
case 'ok':
|
|
1800
|
+
return __WEBPACK_EXTERNAL_MODULE_chalk__["default"].green('✓');
|
|
1801
|
+
case 'warn':
|
|
1802
|
+
return __WEBPACK_EXTERNAL_MODULE_chalk__["default"].yellow('⚠');
|
|
1803
|
+
case 'error':
|
|
1804
|
+
return __WEBPACK_EXTERNAL_MODULE_chalk__["default"].red('✗');
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
function execCommand(command) {
|
|
1808
|
+
try {
|
|
1809
|
+
return (0, __WEBPACK_EXTERNAL_MODULE_node_child_process__.execSync)(command, {
|
|
1810
|
+
encoding: 'utf-8',
|
|
1811
|
+
stdio: [
|
|
1812
|
+
'pipe',
|
|
1813
|
+
'pipe',
|
|
1814
|
+
'pipe'
|
|
1815
|
+
]
|
|
1816
|
+
}).trim();
|
|
1817
|
+
} catch {
|
|
1818
|
+
return null;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
function getDirSize(dirPath) {
|
|
1822
|
+
if (!(0, external_node_fs_.existsSync)(dirPath)) return 0;
|
|
1823
|
+
let size = 0;
|
|
1824
|
+
try {
|
|
1825
|
+
const items = (0, external_node_fs_.readdirSync)(dirPath, {
|
|
1826
|
+
withFileTypes: true
|
|
1827
|
+
});
|
|
1828
|
+
for (const item of items){
|
|
1829
|
+
const itemPath = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(dirPath, item.name);
|
|
1830
|
+
if (item.isDirectory()) size += getDirSize(itemPath);
|
|
1831
|
+
else if (item.isFile()) size += (0, external_node_fs_.statSync)(itemPath).size;
|
|
1832
|
+
}
|
|
1833
|
+
} catch {}
|
|
1834
|
+
return size;
|
|
1835
|
+
}
|
|
1836
|
+
function formatBytes(bytes) {
|
|
1837
|
+
if (0 === bytes) return '0 B';
|
|
1838
|
+
const k = 1024;
|
|
1839
|
+
const sizes = [
|
|
1840
|
+
'B',
|
|
1841
|
+
'KB',
|
|
1842
|
+
'MB',
|
|
1843
|
+
'GB'
|
|
1844
|
+
];
|
|
1845
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1846
|
+
return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`;
|
|
1847
|
+
}
|
|
1848
|
+
function countCachedSkills(cacheDir) {
|
|
1849
|
+
if (!(0, external_node_fs_.existsSync)(cacheDir)) return 0;
|
|
1850
|
+
let count = 0;
|
|
1851
|
+
try {
|
|
1852
|
+
const registries = (0, external_node_fs_.readdirSync)(cacheDir, {
|
|
1853
|
+
withFileTypes: true
|
|
1854
|
+
});
|
|
1855
|
+
for (const registry of registries){
|
|
1856
|
+
if (!registry.isDirectory()) continue;
|
|
1857
|
+
const registryPath = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(cacheDir, registry.name);
|
|
1858
|
+
const owners = (0, external_node_fs_.readdirSync)(registryPath, {
|
|
1859
|
+
withFileTypes: true
|
|
1860
|
+
});
|
|
1861
|
+
for (const owner of owners){
|
|
1862
|
+
if (!owner.isDirectory()) continue;
|
|
1863
|
+
const ownerPath = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(registryPath, owner.name);
|
|
1864
|
+
const repos = (0, external_node_fs_.readdirSync)(ownerPath, {
|
|
1865
|
+
withFileTypes: true
|
|
1866
|
+
});
|
|
1867
|
+
for (const repo of repos){
|
|
1868
|
+
if (!repo.isDirectory()) continue;
|
|
1869
|
+
const repoPath = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(ownerPath, repo.name);
|
|
1870
|
+
const versions = (0, external_node_fs_.readdirSync)(repoPath, {
|
|
1871
|
+
withFileTypes: true
|
|
1872
|
+
});
|
|
1873
|
+
count += versions.filter((v)=>v.isDirectory()).length;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
} catch {}
|
|
1878
|
+
return count;
|
|
1879
|
+
}
|
|
1880
|
+
async function checkReskillVersion(currentVersion, packageName) {
|
|
1881
|
+
try {
|
|
1882
|
+
const result = await checkForUpdate(packageName, currentVersion);
|
|
1883
|
+
if (result?.hasUpdate) return {
|
|
1884
|
+
name: 'reskill version',
|
|
1885
|
+
status: 'warn',
|
|
1886
|
+
message: `${currentVersion} (latest: ${result.latest})`,
|
|
1887
|
+
hint: `Run: npm i -g ${packageName}@latest`
|
|
1888
|
+
};
|
|
1889
|
+
return {
|
|
1890
|
+
name: 'reskill version',
|
|
1891
|
+
status: 'ok',
|
|
1892
|
+
message: `${currentVersion} (latest)`
|
|
1893
|
+
};
|
|
1894
|
+
} catch {
|
|
1895
|
+
return {
|
|
1896
|
+
name: 'reskill version',
|
|
1897
|
+
status: 'ok',
|
|
1898
|
+
message: currentVersion
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
function checkNodeVersion() {
|
|
1903
|
+
const version = process.version;
|
|
1904
|
+
const major = Number.parseInt(version.slice(1).split('.')[0], 10);
|
|
1905
|
+
const required = 18;
|
|
1906
|
+
if (major < required) return {
|
|
1907
|
+
name: 'Node.js version',
|
|
1908
|
+
status: 'error',
|
|
1909
|
+
message: `${version} (requires >=${required}.0.0)`,
|
|
1910
|
+
hint: 'Please upgrade Node.js to version 18 or higher'
|
|
1911
|
+
};
|
|
1912
|
+
return {
|
|
1913
|
+
name: 'Node.js version',
|
|
1914
|
+
status: 'ok',
|
|
1915
|
+
message: `${version} (>=${required}.0.0 required)`
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
function checkGitVersion() {
|
|
1919
|
+
const version = execCommand('git --version');
|
|
1920
|
+
if (!version) return {
|
|
1921
|
+
name: 'Git',
|
|
1922
|
+
status: 'error',
|
|
1923
|
+
message: 'not found',
|
|
1924
|
+
hint: 'Please install Git: https://git-scm.com/downloads'
|
|
1925
|
+
};
|
|
1926
|
+
const match = version.match(/git version (\d+\.\d+\.\d+)/);
|
|
1927
|
+
const versionNum = match ? match[1] : version;
|
|
1928
|
+
return {
|
|
1929
|
+
name: 'Git',
|
|
1930
|
+
status: 'ok',
|
|
1931
|
+
message: versionNum
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
function checkGitAuth() {
|
|
1935
|
+
const home = (0, __WEBPACK_EXTERNAL_MODULE_node_os__.homedir)();
|
|
1936
|
+
const sshDir = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(home, '.ssh');
|
|
1937
|
+
const sshKeyFiles = [
|
|
1938
|
+
'id_rsa',
|
|
1939
|
+
'id_ed25519',
|
|
1940
|
+
'id_ecdsa',
|
|
1941
|
+
'id_dsa'
|
|
1942
|
+
];
|
|
1943
|
+
let hasSSHKey = false;
|
|
1944
|
+
if ((0, external_node_fs_.existsSync)(sshDir)) {
|
|
1945
|
+
for (const keyFile of sshKeyFiles)if ((0, external_node_fs_.existsSync)((0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(sshDir, keyFile))) {
|
|
1946
|
+
hasSSHKey = true;
|
|
1947
|
+
break;
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
const credentialHelper = execCommand('git config --global credential.helper');
|
|
1951
|
+
const hasCredentialHelper = !!credentialHelper;
|
|
1952
|
+
if (hasSSHKey && hasCredentialHelper) return {
|
|
1953
|
+
name: 'Git authentication',
|
|
1954
|
+
status: 'ok',
|
|
1955
|
+
message: 'SSH key + credential helper configured'
|
|
1956
|
+
};
|
|
1957
|
+
if (hasSSHKey) return {
|
|
1958
|
+
name: 'Git authentication',
|
|
1959
|
+
status: 'ok',
|
|
1960
|
+
message: 'SSH key found'
|
|
1961
|
+
};
|
|
1962
|
+
if (hasCredentialHelper) return {
|
|
1963
|
+
name: 'Git authentication',
|
|
1964
|
+
status: 'ok',
|
|
1965
|
+
message: `credential helper: ${credentialHelper}`
|
|
1966
|
+
};
|
|
1967
|
+
return {
|
|
1968
|
+
name: 'Git authentication',
|
|
1969
|
+
status: 'warn',
|
|
1970
|
+
message: 'no SSH key or credential helper found',
|
|
1971
|
+
hint: 'For private repos, add SSH key: ssh-keygen -t ed25519'
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
function checkCacheDir() {
|
|
1975
|
+
const cacheDir = process.env.RESKILL_CACHE_DIR || (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)((0, __WEBPACK_EXTERNAL_MODULE_node_os__.homedir)(), '.reskill-cache');
|
|
1976
|
+
if (!(0, external_node_fs_.existsSync)(cacheDir)) return {
|
|
1977
|
+
name: 'Cache directory',
|
|
1978
|
+
status: 'ok',
|
|
1979
|
+
message: `${cacheDir} (not created yet)`
|
|
1980
|
+
};
|
|
1981
|
+
const size = getDirSize(cacheDir);
|
|
1982
|
+
const count = countCachedSkills(cacheDir);
|
|
1983
|
+
return {
|
|
1984
|
+
name: 'Cache directory',
|
|
1985
|
+
status: 'ok',
|
|
1986
|
+
message: `${cacheDir} (${formatBytes(size)}, ${count} skill${1 !== count ? 's' : ''} cached)`
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
function checkSkillsJson(cwd) {
|
|
1990
|
+
const configLoader = new ConfigLoader(cwd);
|
|
1991
|
+
if (!configLoader.exists()) return {
|
|
1992
|
+
name: 'skills.json',
|
|
1993
|
+
status: 'warn',
|
|
1994
|
+
message: 'not found',
|
|
1995
|
+
hint: 'Run: reskill init'
|
|
1996
|
+
};
|
|
1997
|
+
try {
|
|
1998
|
+
const skills = configLoader.getSkills();
|
|
1999
|
+
const count = Object.keys(skills).length;
|
|
2000
|
+
return {
|
|
2001
|
+
name: 'skills.json',
|
|
2002
|
+
status: 'ok',
|
|
2003
|
+
message: `found (${count} skill${1 !== count ? 's' : ''} declared)`
|
|
2004
|
+
};
|
|
2005
|
+
} catch (error) {
|
|
2006
|
+
return {
|
|
2007
|
+
name: 'skills.json',
|
|
2008
|
+
status: 'error',
|
|
2009
|
+
message: `invalid format: ${error.message}`,
|
|
2010
|
+
hint: 'Check skills.json syntax'
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
const RESERVED_REGISTRIES = [
|
|
2015
|
+
'github',
|
|
2016
|
+
'gitlab'
|
|
2017
|
+
];
|
|
2018
|
+
const DANGEROUS_INSTALL_DIRS = [
|
|
2019
|
+
'src',
|
|
2020
|
+
'lib',
|
|
2021
|
+
'dist',
|
|
2022
|
+
'build',
|
|
2023
|
+
'out',
|
|
2024
|
+
'node_modules',
|
|
2025
|
+
'.git',
|
|
2026
|
+
'.github',
|
|
2027
|
+
'.gitlab',
|
|
2028
|
+
'.vscode',
|
|
2029
|
+
'.idea'
|
|
2030
|
+
];
|
|
2031
|
+
const DANGEROUS_SKILL_NAMES = [
|
|
2032
|
+
'.git',
|
|
2033
|
+
'..',
|
|
2034
|
+
'.',
|
|
2035
|
+
'node_modules',
|
|
2036
|
+
'__proto__',
|
|
2037
|
+
'constructor'
|
|
2038
|
+
];
|
|
2039
|
+
const NETWORK_CHECK_TIMEOUT_MS = 5000;
|
|
2040
|
+
const CHECK_NAME_PAD_WIDTH = 26;
|
|
2041
|
+
function checkRegistryConflicts(cwd) {
|
|
2042
|
+
const results = [];
|
|
2043
|
+
const configLoader = new ConfigLoader(cwd);
|
|
2044
|
+
if (!configLoader.exists()) return results;
|
|
2045
|
+
try {
|
|
2046
|
+
const config = configLoader.load();
|
|
2047
|
+
const registries = config.registries || {};
|
|
2048
|
+
for (const name of Object.keys(registries))if (RESERVED_REGISTRIES.includes(name.toLowerCase())) results.push({
|
|
2049
|
+
name: 'Registry conflict',
|
|
2050
|
+
status: 'warn',
|
|
2051
|
+
message: `"${name}" overrides built-in registry`,
|
|
2052
|
+
hint: 'Consider using a different name for custom registries'
|
|
2053
|
+
});
|
|
2054
|
+
} catch {}
|
|
2055
|
+
return results;
|
|
2056
|
+
}
|
|
2057
|
+
function checkInstallDir(cwd) {
|
|
2058
|
+
const configLoader = new ConfigLoader(cwd);
|
|
2059
|
+
if (!configLoader.exists()) return null;
|
|
2060
|
+
try {
|
|
2061
|
+
const defaults = configLoader.getDefaults();
|
|
2062
|
+
const installDir = defaults.installDir;
|
|
2063
|
+
if (!installDir) return null;
|
|
2064
|
+
const normalizedDir = installDir.replace(/^\.\//, '').replace(/\/$/, '');
|
|
2065
|
+
if (DANGEROUS_INSTALL_DIRS.includes(normalizedDir.toLowerCase())) return {
|
|
2066
|
+
name: 'Dangerous installDir',
|
|
2067
|
+
status: 'error',
|
|
2068
|
+
message: `"${installDir}" may conflict with important directories`,
|
|
2069
|
+
hint: 'Use a dedicated directory like ".skills" or ".agents/skills"'
|
|
2070
|
+
};
|
|
2071
|
+
if (installDir.includes('..')) return {
|
|
2072
|
+
name: 'Dangerous installDir',
|
|
2073
|
+
status: 'error',
|
|
2074
|
+
message: `"${installDir}" contains path traversal`,
|
|
2075
|
+
hint: 'Use a simple directory name without ".."'
|
|
2076
|
+
};
|
|
2077
|
+
} catch {}
|
|
2078
|
+
return null;
|
|
2079
|
+
}
|
|
2080
|
+
function checkTargetAgents(cwd) {
|
|
2081
|
+
const results = [];
|
|
2082
|
+
const configLoader = new ConfigLoader(cwd);
|
|
2083
|
+
if (!configLoader.exists()) return results;
|
|
2084
|
+
try {
|
|
2085
|
+
const defaults = configLoader.getDefaults();
|
|
2086
|
+
const targetAgents = defaults.targetAgents || [];
|
|
2087
|
+
for (const agent of targetAgents)if (!isValidAgentType(agent)) results.push({
|
|
2088
|
+
name: 'Invalid agent',
|
|
2089
|
+
status: 'warn',
|
|
2090
|
+
message: `Unknown agent type: "${agent}"`,
|
|
2091
|
+
hint: 'Run: reskill install --help to see valid agent types'
|
|
2092
|
+
});
|
|
2093
|
+
} catch {}
|
|
2094
|
+
return results;
|
|
2095
|
+
}
|
|
2096
|
+
function checkSkillRefs(cwd) {
|
|
2097
|
+
const results = [];
|
|
2098
|
+
const configLoader = new ConfigLoader(cwd);
|
|
2099
|
+
if (!configLoader.exists()) return results;
|
|
2100
|
+
try {
|
|
2101
|
+
const skills = configLoader.getSkills();
|
|
2102
|
+
const resolver = new GitResolver();
|
|
2103
|
+
for (const [name, ref] of Object.entries(skills)){
|
|
2104
|
+
if (DANGEROUS_SKILL_NAMES.includes(name) || name.includes('/') || name.includes('\\')) {
|
|
2105
|
+
results.push({
|
|
2106
|
+
name: 'Dangerous skill name',
|
|
2107
|
+
status: 'error',
|
|
2108
|
+
message: `"${name}" is not a valid skill name`,
|
|
2109
|
+
hint: 'Use alphanumeric names with hyphens or underscores'
|
|
2110
|
+
});
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
try {
|
|
2114
|
+
resolver.parseRef(ref);
|
|
2115
|
+
} catch (error) {
|
|
2116
|
+
results.push({
|
|
2117
|
+
name: 'Invalid skill ref',
|
|
2118
|
+
status: 'error',
|
|
2119
|
+
message: `"${name}": ${error.message}`,
|
|
2120
|
+
hint: 'Format: registry:owner/repo@version or owner/repo@version'
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
} catch {}
|
|
2125
|
+
return results;
|
|
2126
|
+
}
|
|
2127
|
+
function checkMonorepoVersions(cwd) {
|
|
2128
|
+
const results = [];
|
|
2129
|
+
const configLoader = new ConfigLoader(cwd);
|
|
2130
|
+
if (!configLoader.exists()) return results;
|
|
2131
|
+
try {
|
|
2132
|
+
const skills = configLoader.getSkills();
|
|
2133
|
+
const resolver = new GitResolver();
|
|
2134
|
+
const repoVersions = new Map();
|
|
2135
|
+
for (const [name, ref] of Object.entries(skills))try {
|
|
2136
|
+
const parsed = resolver.parseRef(ref);
|
|
2137
|
+
if (!parsed.subPath) continue;
|
|
2138
|
+
const repoKey = `${parsed.registry}:${parsed.owner}/${parsed.repo}`;
|
|
2139
|
+
const version = parsed.version || 'default';
|
|
2140
|
+
let versions = repoVersions.get(repoKey);
|
|
2141
|
+
if (!versions) {
|
|
2142
|
+
versions = new Map();
|
|
2143
|
+
repoVersions.set(repoKey, versions);
|
|
2144
|
+
}
|
|
2145
|
+
let skillList = versions.get(version);
|
|
2146
|
+
if (!skillList) {
|
|
2147
|
+
skillList = [];
|
|
2148
|
+
versions.set(version, skillList);
|
|
2149
|
+
}
|
|
2150
|
+
skillList.push(name);
|
|
2151
|
+
} catch {}
|
|
2152
|
+
for (const [repo, versions] of repoVersions)if (versions.size > 1) {
|
|
2153
|
+
const versionList = [
|
|
2154
|
+
...versions.entries()
|
|
2155
|
+
].map(([v, skills])=>`${v} (${skills.join(', ')})`).join(', ');
|
|
2156
|
+
results.push({
|
|
2157
|
+
name: 'Version mismatch',
|
|
2158
|
+
status: 'warn',
|
|
2159
|
+
message: `${repo} has multiple versions: ${versionList}`,
|
|
2160
|
+
hint: 'Consider using the same version for all skills from this repo'
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
} catch {}
|
|
2164
|
+
return results;
|
|
2165
|
+
}
|
|
2166
|
+
function checkSkillsLock(cwd) {
|
|
2167
|
+
const configLoader = new ConfigLoader(cwd);
|
|
2168
|
+
const lockManager = new LockManager(cwd);
|
|
2169
|
+
if (!configLoader.exists()) return {
|
|
2170
|
+
name: 'skills.lock',
|
|
2171
|
+
status: 'ok',
|
|
2172
|
+
message: 'n/a (no skills.json)'
|
|
2173
|
+
};
|
|
2174
|
+
if (!lockManager.exists()) {
|
|
2175
|
+
const skills = configLoader.getSkills();
|
|
2176
|
+
if (0 === Object.keys(skills).length) return {
|
|
2177
|
+
name: 'skills.lock',
|
|
2178
|
+
status: 'ok',
|
|
2179
|
+
message: 'n/a (no skills declared)'
|
|
2180
|
+
};
|
|
2181
|
+
return {
|
|
2182
|
+
name: 'skills.lock',
|
|
2183
|
+
status: 'warn',
|
|
2184
|
+
message: 'not found',
|
|
2185
|
+
hint: 'Run: reskill install'
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
const configSkills = configLoader.getSkills();
|
|
2189
|
+
const lockedSkills = lockManager.getAll();
|
|
2190
|
+
const configNames = new Set(Object.keys(configSkills));
|
|
2191
|
+
const lockedNames = new Set(Object.keys(lockedSkills));
|
|
2192
|
+
const missingInLock = [
|
|
2193
|
+
...configNames
|
|
2194
|
+
].filter((n)=>!lockedNames.has(n));
|
|
2195
|
+
const extraInLock = [
|
|
2196
|
+
...lockedNames
|
|
2197
|
+
].filter((n)=>!configNames.has(n));
|
|
2198
|
+
if (missingInLock.length > 0) {
|
|
2199
|
+
const displayMissing = missingInLock.length > 3 ? `${missingInLock.slice(0, 3).join(', ')} +${missingInLock.length - 3} more` : missingInLock.join(', ');
|
|
2200
|
+
return {
|
|
2201
|
+
name: 'skills.lock',
|
|
2202
|
+
status: 'warn',
|
|
2203
|
+
message: `out of sync (missing: ${displayMissing})`,
|
|
2204
|
+
hint: 'Run: reskill install'
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
if (extraInLock.length > 0) {
|
|
2208
|
+
const displayExtra = extraInLock.length > 3 ? `${extraInLock.slice(0, 3).join(', ')} +${extraInLock.length - 3} more` : extraInLock.join(', ');
|
|
2209
|
+
return {
|
|
2210
|
+
name: 'skills.lock',
|
|
2211
|
+
status: 'warn',
|
|
2212
|
+
message: `out of sync (extra: ${displayExtra})`,
|
|
2213
|
+
hint: 'Run: reskill install'
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
return {
|
|
2217
|
+
name: 'skills.lock',
|
|
2218
|
+
status: 'ok',
|
|
2219
|
+
message: `in sync (${lockedNames.size} skill${1 !== lockedNames.size ? 's' : ''} locked)`
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
function isValidSymlink(path) {
|
|
2223
|
+
try {
|
|
2224
|
+
const stat = (0, external_node_fs_.lstatSync)(path);
|
|
2225
|
+
if (!stat.isSymbolicLink()) return true;
|
|
2226
|
+
(0, external_node_fs_.realpathSync)(path);
|
|
2227
|
+
return true;
|
|
2228
|
+
} catch {
|
|
2229
|
+
return false;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
function validateSkillJson(skillJsonPath) {
|
|
2233
|
+
if (!(0, external_node_fs_.existsSync)(skillJsonPath)) return {
|
|
2234
|
+
valid: true
|
|
2235
|
+
};
|
|
2236
|
+
try {
|
|
2237
|
+
const content = (0, external_node_fs_.readFileSync)(skillJsonPath, 'utf-8');
|
|
2238
|
+
const json = JSON.parse(content);
|
|
2239
|
+
if (!json.name) return {
|
|
2240
|
+
valid: false,
|
|
2241
|
+
error: 'skill.json missing "name" field'
|
|
2242
|
+
};
|
|
2243
|
+
return {
|
|
2244
|
+
valid: true
|
|
2245
|
+
};
|
|
2246
|
+
} catch (error) {
|
|
2247
|
+
if (error instanceof SyntaxError) return {
|
|
2248
|
+
valid: false,
|
|
2249
|
+
error: 'skill.json is not valid JSON'
|
|
2250
|
+
};
|
|
2251
|
+
return {
|
|
2252
|
+
valid: false,
|
|
2253
|
+
error: `skill.json read error: ${error.message}`
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
function checkInstalledSkills(cwd) {
|
|
2258
|
+
const results = [];
|
|
2259
|
+
const skillManager = new SkillManager(cwd);
|
|
2260
|
+
const installed = skillManager.list();
|
|
2261
|
+
if (0 === installed.length) return [
|
|
2262
|
+
{
|
|
2263
|
+
name: 'Installed skills',
|
|
2264
|
+
status: 'ok',
|
|
2265
|
+
message: 'none'
|
|
2266
|
+
}
|
|
2267
|
+
];
|
|
2268
|
+
const issues = [];
|
|
2269
|
+
for (const skill of installed){
|
|
2270
|
+
const skillJsonPath = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(skill.path, 'skill.json');
|
|
2271
|
+
const skillMdPath = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(skill.path, 'SKILL.md');
|
|
2272
|
+
if (skill.isLinked && !isValidSymlink(skill.path)) {
|
|
2273
|
+
issues.push({
|
|
2274
|
+
name: skill.name,
|
|
2275
|
+
reason: 'symlink target does not exist',
|
|
2276
|
+
severity: 'error'
|
|
2277
|
+
});
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2280
|
+
const hasSkillJson = (0, external_node_fs_.existsSync)(skillJsonPath);
|
|
2281
|
+
const hasSkillMd = (0, external_node_fs_.existsSync)(skillMdPath);
|
|
2282
|
+
if (!hasSkillJson && !hasSkillMd) {
|
|
2283
|
+
issues.push({
|
|
2284
|
+
name: skill.name,
|
|
2285
|
+
reason: 'missing both skill.json and SKILL.md',
|
|
2286
|
+
severity: 'error'
|
|
2287
|
+
});
|
|
2288
|
+
continue;
|
|
2289
|
+
}
|
|
2290
|
+
if (hasSkillJson) {
|
|
2291
|
+
const validation = validateSkillJson(skillJsonPath);
|
|
2292
|
+
if (!validation.valid) issues.push({
|
|
2293
|
+
name: skill.name,
|
|
2294
|
+
reason: validation.error || 'invalid skill.json',
|
|
2295
|
+
severity: 'warn'
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
const errorCount = issues.filter((i)=>'error' === i.severity).length;
|
|
2300
|
+
const warnCount = issues.filter((i)=>'warn' === i.severity).length;
|
|
2301
|
+
if (0 === issues.length) results.push({
|
|
2302
|
+
name: 'Installed skills',
|
|
2303
|
+
status: 'ok',
|
|
2304
|
+
message: `${installed.length} skill${1 !== installed.length ? 's' : ''} installed`
|
|
2305
|
+
});
|
|
2306
|
+
else {
|
|
2307
|
+
const status = errorCount > 0 ? 'error' : 'warn';
|
|
2308
|
+
const issueText = errorCount > 0 ? `${errorCount} broken` : `${warnCount} with issues`;
|
|
2309
|
+
results.push({
|
|
2310
|
+
name: 'Installed skills',
|
|
2311
|
+
status,
|
|
2312
|
+
message: `${installed.length} installed, ${issueText}`,
|
|
2313
|
+
hint: 'Run: reskill install --force to fix'
|
|
2314
|
+
});
|
|
2315
|
+
for (const issue of issues)results.push({
|
|
2316
|
+
name: ` └─ ${issue.name}`,
|
|
2317
|
+
status: 'error' === issue.severity ? 'error' : 'warn',
|
|
2318
|
+
message: issue.reason
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
return results;
|
|
2322
|
+
}
|
|
2323
|
+
async function checkNetwork(host) {
|
|
2324
|
+
const displayName = host.replace('https://', '').replace('http://', '');
|
|
2325
|
+
try {
|
|
2326
|
+
const controller = new AbortController();
|
|
2327
|
+
const timeout = setTimeout(()=>controller.abort(), NETWORK_CHECK_TIMEOUT_MS);
|
|
2328
|
+
const response = await fetch(host, {
|
|
2329
|
+
method: 'HEAD',
|
|
2330
|
+
signal: controller.signal
|
|
2331
|
+
});
|
|
2332
|
+
clearTimeout(timeout);
|
|
2333
|
+
if (response.ok || 301 === response.status || 302 === response.status) return {
|
|
2334
|
+
name: `Network (${displayName})`,
|
|
2335
|
+
status: 'ok',
|
|
2336
|
+
message: 'reachable'
|
|
2337
|
+
};
|
|
2338
|
+
return {
|
|
2339
|
+
name: `Network (${displayName})`,
|
|
2340
|
+
status: 'warn',
|
|
2341
|
+
message: `status ${response.status}`
|
|
2342
|
+
};
|
|
2343
|
+
} catch (error) {
|
|
2344
|
+
const errorMessage = error instanceof Error ? error.message : 'unknown error';
|
|
2345
|
+
return {
|
|
2346
|
+
name: `Network (${displayName})`,
|
|
2347
|
+
status: 'error',
|
|
2348
|
+
message: `unreachable (${errorMessage})`,
|
|
2349
|
+
hint: 'Check your internet connection or firewall settings'
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
function printResult(result) {
|
|
2354
|
+
const icon = getStatusIcon(result.status);
|
|
2355
|
+
const name = result.name.padEnd(CHECK_NAME_PAD_WIDTH);
|
|
2356
|
+
const message = 'ok' === result.status ? result.message : __WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(result.message);
|
|
2357
|
+
logger_logger.log(`${icon} ${name} ${message}`);
|
|
2358
|
+
if (result.hint) logger_logger.log(` ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim('→')} ${__WEBPACK_EXTERNAL_MODULE_chalk__["default"].dim(result.hint)}`);
|
|
2359
|
+
}
|
|
2360
|
+
async function runDoctorChecks(options) {
|
|
2361
|
+
const { cwd, packageName, packageVersion, skipNetwork, skipConfigChecks } = options;
|
|
2362
|
+
const results = [];
|
|
2363
|
+
results.push(await checkReskillVersion(packageVersion, packageName));
|
|
2364
|
+
results.push(checkNodeVersion());
|
|
2365
|
+
results.push(checkGitVersion());
|
|
2366
|
+
results.push(checkGitAuth());
|
|
2367
|
+
results.push(checkCacheDir());
|
|
2368
|
+
results.push(checkSkillsJson(cwd));
|
|
2369
|
+
results.push(checkSkillsLock(cwd));
|
|
2370
|
+
results.push(...checkInstalledSkills(cwd));
|
|
2371
|
+
if (!skipConfigChecks) {
|
|
2372
|
+
results.push(...checkRegistryConflicts(cwd));
|
|
2373
|
+
const installDirCheck = checkInstallDir(cwd);
|
|
2374
|
+
if (installDirCheck) results.push(installDirCheck);
|
|
2375
|
+
results.push(...checkTargetAgents(cwd));
|
|
2376
|
+
results.push(...checkSkillRefs(cwd));
|
|
2377
|
+
results.push(...checkMonorepoVersions(cwd));
|
|
2378
|
+
}
|
|
2379
|
+
if (!skipNetwork) {
|
|
2380
|
+
results.push(await checkNetwork('https://github.com'));
|
|
2381
|
+
results.push(await checkNetwork('https://gitlab.com'));
|
|
2382
|
+
}
|
|
2383
|
+
return results;
|
|
2384
|
+
}
|
|
2385
|
+
const doctorCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('doctor').description('Diagnose reskill environment and check for issues').option('--json', 'Output as JSON').option('--skip-network', 'Skip network connectivity checks').action(async (options)=>{
|
|
2386
|
+
const __dirname = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.dirname)((0, __WEBPACK_EXTERNAL_MODULE_node_url__.fileURLToPath)(import.meta.url));
|
|
2387
|
+
const packageJson = JSON.parse((0, external_node_fs_.readFileSync)((0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(__dirname, '../../package.json'), 'utf-8'));
|
|
2388
|
+
if (!options.json) logger_logger.log(__WEBPACK_EXTERNAL_MODULE_chalk__["default"].bold('\n🩺 Checking reskill environment...\n'));
|
|
2389
|
+
const results = await runDoctorChecks({
|
|
2390
|
+
cwd: process.cwd(),
|
|
2391
|
+
packageName: packageJson.name,
|
|
2392
|
+
packageVersion: packageJson.version,
|
|
2393
|
+
skipNetwork: options.skipNetwork
|
|
2394
|
+
});
|
|
2395
|
+
if (options.json) {
|
|
2396
|
+
console.log(JSON.stringify(results, null, 2));
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
for (const result of results)printResult(result);
|
|
2400
|
+
const errors = results.filter((r)=>'error' === r.status).length;
|
|
2401
|
+
const warnings = results.filter((r)=>'warn' === r.status).length;
|
|
2402
|
+
logger_logger.newline();
|
|
2403
|
+
if (errors > 0) {
|
|
2404
|
+
logger_logger.error(`Found ${errors} error${1 !== errors ? 's' : ''} and ${warnings} warning${1 !== warnings ? 's' : ''}`);
|
|
2405
|
+
process.exit(1);
|
|
2406
|
+
}
|
|
2407
|
+
if (warnings > 0) logger_logger.warn(`Found ${warnings} warning${1 !== warnings ? 's' : ''}, but reskill should work`);
|
|
2408
|
+
else logger_logger.success('All checks passed! reskill is ready to use.');
|
|
2409
|
+
});
|
|
1804
2410
|
const infoCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('info').description('Show skill details').argument('<skill>', 'Skill name').option('-j, --json', 'Output as JSON').action((skillName, options)=>{
|
|
1805
2411
|
const skillManager = new SkillManager();
|
|
1806
2412
|
const info = skillManager.getInfo(skillName);
|
|
@@ -1840,29 +2446,29 @@ const infoCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('info').de
|
|
|
1840
2446
|
}
|
|
1841
2447
|
} else logger_logger.warn(`Skill ${skillName} is not installed`);
|
|
1842
2448
|
});
|
|
1843
|
-
const
|
|
2449
|
+
const DEFAULT_INSTALL_DIR = '.skills';
|
|
2450
|
+
function displayConfigSummary(installDir) {
|
|
2451
|
+
logger_logger.success('Created skills.json');
|
|
2452
|
+
logger_logger.newline();
|
|
2453
|
+
logger_logger.log('Configuration:');
|
|
2454
|
+
logger_logger.log(` Install directory: ${installDir}`);
|
|
2455
|
+
logger_logger.newline();
|
|
2456
|
+
logger_logger.log('Next steps:');
|
|
2457
|
+
logger_logger.log(' reskill install <skill> Install a skill');
|
|
2458
|
+
logger_logger.log(' reskill list List installed skills');
|
|
2459
|
+
}
|
|
2460
|
+
const initCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('init').description('Initialize a new skills.json configuration').option('-d, --install-dir <dir>', 'Skills installation directory', DEFAULT_INSTALL_DIR).option('-y, --yes', 'Skip prompts and use defaults').action((options)=>{
|
|
1844
2461
|
const configLoader = new ConfigLoader();
|
|
1845
2462
|
if (configLoader.exists()) {
|
|
1846
2463
|
logger_logger.warn('skills.json already exists');
|
|
1847
2464
|
return;
|
|
1848
2465
|
}
|
|
1849
|
-
|
|
1850
|
-
name: options.name,
|
|
2466
|
+
configLoader.create({
|
|
1851
2467
|
defaults: {
|
|
1852
|
-
registry: options.registry,
|
|
1853
2468
|
installDir: options.installDir
|
|
1854
2469
|
}
|
|
1855
2470
|
});
|
|
1856
|
-
|
|
1857
|
-
logger_logger.newline();
|
|
1858
|
-
logger_logger.log('Configuration:');
|
|
1859
|
-
logger_logger.log(` Name: ${config.name || '(not set)'}`);
|
|
1860
|
-
logger_logger.log(` Default registry: ${config.defaults?.registry}`);
|
|
1861
|
-
logger_logger.log(` Install directory: ${config.defaults?.installDir}`);
|
|
1862
|
-
logger_logger.newline();
|
|
1863
|
-
logger_logger.log('Next steps:');
|
|
1864
|
-
logger_logger.log(' reskill install <skill> Install a skill');
|
|
1865
|
-
logger_logger.log(' reskill list List installed skills');
|
|
2471
|
+
displayConfigSummary(options.installDir);
|
|
1866
2472
|
});
|
|
1867
2473
|
function formatAgentNames(agentTypes, maxShow = 5) {
|
|
1868
2474
|
const names = agentTypes.map((a)=>agents[a].displayName);
|
|
@@ -2327,9 +2933,9 @@ const updateCommand = new __WEBPACK_EXTERNAL_MODULE_commander__.Command('update'
|
|
|
2327
2933
|
});
|
|
2328
2934
|
if (maybeHandleCompletion()) process.exit(0);
|
|
2329
2935
|
const cli_rslib_entry_dirname = (0, __WEBPACK_EXTERNAL_MODULE_node_path__.dirname)((0, __WEBPACK_EXTERNAL_MODULE_node_url__.fileURLToPath)(import.meta.url));
|
|
2330
|
-
const
|
|
2936
|
+
const cli_rslib_entry_packageJson = JSON.parse((0, external_node_fs_.readFileSync)((0, __WEBPACK_EXTERNAL_MODULE_node_path__.join)(cli_rslib_entry_dirname, '../../package.json'), 'utf-8'));
|
|
2331
2937
|
const program = new __WEBPACK_EXTERNAL_MODULE_commander__.Command();
|
|
2332
|
-
program.name('reskill').description('AI Skills Package Manager - Git-based skills management for AI agents').version(
|
|
2938
|
+
program.name('reskill').description('AI Skills Package Manager - Git-based skills management for AI agents').version(cli_rslib_entry_packageJson.version);
|
|
2333
2939
|
program.addCommand(initCommand);
|
|
2334
2940
|
program.addCommand(installCommand);
|
|
2335
2941
|
program.addCommand(listCommand);
|
|
@@ -2338,7 +2944,8 @@ program.addCommand(updateCommand);
|
|
|
2338
2944
|
program.addCommand(outdatedCommand);
|
|
2339
2945
|
program.addCommand(uninstallCommand);
|
|
2340
2946
|
program.addCommand(completionCommand);
|
|
2341
|
-
|
|
2947
|
+
program.addCommand(doctorCommand);
|
|
2948
|
+
const updateCheckPromise = checkForUpdate(cli_rslib_entry_packageJson.name, cli_rslib_entry_packageJson.version);
|
|
2342
2949
|
program.parseAsync().then(async ()=>{
|
|
2343
2950
|
const result = await updateCheckPromise;
|
|
2344
2951
|
if (result?.hasUpdate) logger_logger.log(formatUpdateMessage(result));
|