@localskills/cli 0.12.0 → 0.12.1
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/dist/index.js +255 -124
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1144,7 +1144,7 @@ ${l}
|
|
|
1144
1144
|
} }).prompt();
|
|
1145
1145
|
|
|
1146
1146
|
// src/lib/config.ts
|
|
1147
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, copyFileSync } from "fs";
|
|
1147
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, copyFileSync, renameSync } from "fs";
|
|
1148
1148
|
import { join } from "path";
|
|
1149
1149
|
import { homedir } from "os";
|
|
1150
1150
|
var CONFIG_DIR = join(homedir(), ".localskills");
|
|
@@ -1214,6 +1214,21 @@ function validateV3(config) {
|
|
|
1214
1214
|
if (!config.profiles[DEFAULT_PROFILE_NAME]) {
|
|
1215
1215
|
config.profiles[DEFAULT_PROFILE_NAME] = { ...DEFAULT_PROFILE, installed_skills: {} };
|
|
1216
1216
|
}
|
|
1217
|
+
for (const name of Object.keys(config.profiles)) {
|
|
1218
|
+
const profile = config.profiles[name];
|
|
1219
|
+
if (!profile || typeof profile !== "object") {
|
|
1220
|
+
config.profiles[name] = { ...DEFAULT_PROFILE, installed_skills: {} };
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
if (!profile.installed_skills || typeof profile.installed_skills !== "object") {
|
|
1224
|
+
profile.installed_skills = {};
|
|
1225
|
+
}
|
|
1226
|
+
if (!profile.defaults || typeof profile.defaults !== "object") {
|
|
1227
|
+
profile.defaults = { ...DEFAULT_PROFILE.defaults };
|
|
1228
|
+
}
|
|
1229
|
+
if (profile.token === void 0) profile.token = null;
|
|
1230
|
+
if (profile.anonymous_key === void 0) profile.anonymous_key = null;
|
|
1231
|
+
}
|
|
1217
1232
|
if (!config.active_profile || !config.profiles[config.active_profile]) {
|
|
1218
1233
|
config.active_profile = DEFAULT_PROFILE_NAME;
|
|
1219
1234
|
}
|
|
@@ -1266,9 +1281,11 @@ function loadFullConfig() {
|
|
|
1266
1281
|
}
|
|
1267
1282
|
function saveFullConfig(config) {
|
|
1268
1283
|
mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
1269
|
-
|
|
1284
|
+
const tmpPath = `${CONFIG_PATH}.${process.pid}.tmp`;
|
|
1285
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n", {
|
|
1270
1286
|
mode: 384
|
|
1271
1287
|
});
|
|
1288
|
+
renameSync(tmpPath, CONFIG_PATH);
|
|
1272
1289
|
try {
|
|
1273
1290
|
chmodSync(CONFIG_DIR, 448);
|
|
1274
1291
|
chmodSync(CONFIG_PATH, 384);
|
|
@@ -1349,6 +1366,13 @@ function migrateV2toV3(v2) {
|
|
|
1349
1366
|
}
|
|
1350
1367
|
};
|
|
1351
1368
|
}
|
|
1369
|
+
function resolveInstalledSkillKey(config, ref) {
|
|
1370
|
+
if (config.installed_skills[ref]) return ref;
|
|
1371
|
+
for (const [key, record] of Object.entries(config.installed_skills)) {
|
|
1372
|
+
if (record.slug === ref) return key;
|
|
1373
|
+
}
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1352
1376
|
function getToken() {
|
|
1353
1377
|
return loadConfig().token;
|
|
1354
1378
|
}
|
|
@@ -1616,7 +1640,7 @@ var whoamiCommand = new Command("whoami").description("Show current user info").
|
|
|
1616
1640
|
// src/commands/install.ts
|
|
1617
1641
|
import { Command as Command2 } from "commander";
|
|
1618
1642
|
import { mkdirSync as mkdirSync7, rmSync as rmSync4, cpSync } from "fs";
|
|
1619
|
-
import { dirname as dirname5 } from "path";
|
|
1643
|
+
import { dirname as dirname5, resolve as resolvePathAbs } from "path";
|
|
1620
1644
|
|
|
1621
1645
|
// ../../packages/shared/dist/utils/semver.js
|
|
1622
1646
|
var SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
@@ -1669,14 +1693,17 @@ function requireAuth(client) {
|
|
|
1669
1693
|
}
|
|
1670
1694
|
}
|
|
1671
1695
|
function buildVersionQuery(range) {
|
|
1672
|
-
if (!range) return "";
|
|
1696
|
+
if (!range || range === "latest") return "";
|
|
1673
1697
|
if (isValidSemVer(range)) {
|
|
1674
1698
|
return `?semver=${encodeURIComponent(range)}`;
|
|
1675
1699
|
}
|
|
1676
1700
|
if (isValidSemVerRange(range)) {
|
|
1677
1701
|
return `?range=${encodeURIComponent(range)}`;
|
|
1678
1702
|
}
|
|
1679
|
-
console.error(
|
|
1703
|
+
console.error(
|
|
1704
|
+
`Invalid version specifier: ${range}
|
|
1705
|
+
Expected an exact version (1.2.3), a range (^1.2.3, ~1.2.3, >=1.2.3), "*", or "latest".`
|
|
1706
|
+
);
|
|
1680
1707
|
process.exit(1);
|
|
1681
1708
|
}
|
|
1682
1709
|
function formatVersionLabel(semver, version2) {
|
|
@@ -1703,14 +1730,17 @@ import { join as join13, resolve as resolve3 } from "path";
|
|
|
1703
1730
|
import { homedir as homedir8 } from "os";
|
|
1704
1731
|
|
|
1705
1732
|
// src/lib/installers/cursor.ts
|
|
1706
|
-
import { existsSync as
|
|
1733
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1707
1734
|
import { join as join3 } from "path";
|
|
1708
1735
|
import { homedir as homedir2 } from "os";
|
|
1709
1736
|
|
|
1710
1737
|
// src/lib/content-transform.ts
|
|
1711
1738
|
function yamlEscape(value) {
|
|
1712
|
-
|
|
1713
|
-
|
|
1739
|
+
const needsQuoting = /[\r\n:#"']/.test(value) || // structural characters anywhere
|
|
1740
|
+
/^[\s\-?*&!|>%@`{[\]}]/.test(value) || // YAML indicator at the start
|
|
1741
|
+
/\s$/.test(value);
|
|
1742
|
+
if (needsQuoting) {
|
|
1743
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n")}"`;
|
|
1714
1744
|
}
|
|
1715
1745
|
return value;
|
|
1716
1746
|
}
|
|
@@ -1745,7 +1775,7 @@ function stripFrontmatter(content) {
|
|
|
1745
1775
|
}
|
|
1746
1776
|
|
|
1747
1777
|
// src/lib/installers/common.ts
|
|
1748
|
-
import { existsSync as
|
|
1778
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
1749
1779
|
import { join as join2 } from "path";
|
|
1750
1780
|
|
|
1751
1781
|
// src/lib/symlink.ts
|
|
@@ -1799,6 +1829,61 @@ function isSymlinkInto(linkPath, dir) {
|
|
|
1799
1829
|
}
|
|
1800
1830
|
}
|
|
1801
1831
|
|
|
1832
|
+
// src/lib/marked-sections.ts
|
|
1833
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
1834
|
+
import { dirname as dirname2 } from "path";
|
|
1835
|
+
var START_MARKER = (slug) => `<!-- localskills:start:${slug} -->`;
|
|
1836
|
+
var END_MARKER = (slug) => `<!-- localskills:end:${slug} -->`;
|
|
1837
|
+
function upsertSection(filePath, slug, content) {
|
|
1838
|
+
mkdirSync3(dirname2(filePath), { recursive: true });
|
|
1839
|
+
let existing = "";
|
|
1840
|
+
if (existsSync3(filePath)) {
|
|
1841
|
+
existing = readFileSync2(filePath, "utf-8");
|
|
1842
|
+
}
|
|
1843
|
+
const start = START_MARKER(slug);
|
|
1844
|
+
const end = END_MARKER(slug);
|
|
1845
|
+
const section = `${start}
|
|
1846
|
+
${content}
|
|
1847
|
+
${end}`;
|
|
1848
|
+
const startIdx = existing.indexOf(start);
|
|
1849
|
+
const endIdx = startIdx === -1 ? -1 : existing.indexOf(end, startIdx + start.length);
|
|
1850
|
+
let result;
|
|
1851
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
1852
|
+
result = existing.slice(0, startIdx) + section + existing.slice(endIdx + end.length);
|
|
1853
|
+
} else {
|
|
1854
|
+
const separator = existing.length > 0 && !existing.endsWith("\n\n") ? "\n\n" : "";
|
|
1855
|
+
result = existing + separator + section + "\n";
|
|
1856
|
+
}
|
|
1857
|
+
writeFileSync2(filePath, result);
|
|
1858
|
+
}
|
|
1859
|
+
function removeSection(filePath, slug) {
|
|
1860
|
+
if (!existsSync3(filePath)) return false;
|
|
1861
|
+
const existing = readFileSync2(filePath, "utf-8");
|
|
1862
|
+
const start = START_MARKER(slug);
|
|
1863
|
+
const end = END_MARKER(slug);
|
|
1864
|
+
const startIdx = existing.indexOf(start);
|
|
1865
|
+
const endIdx = startIdx === -1 ? -1 : existing.indexOf(end, startIdx + start.length);
|
|
1866
|
+
if (startIdx === -1 || endIdx === -1) return false;
|
|
1867
|
+
let before = existing.slice(0, startIdx);
|
|
1868
|
+
let after = existing.slice(endIdx + end.length);
|
|
1869
|
+
while (before.endsWith("\n\n")) before = before.slice(0, -1);
|
|
1870
|
+
while (after.startsWith("\n\n")) after = after.slice(1);
|
|
1871
|
+
const result = (before + after).trim();
|
|
1872
|
+
writeFileSync2(filePath, result ? result + "\n" : "");
|
|
1873
|
+
return true;
|
|
1874
|
+
}
|
|
1875
|
+
function listSections(filePath) {
|
|
1876
|
+
if (!existsSync3(filePath)) return [];
|
|
1877
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
1878
|
+
const regex = /<!-- localskills:start:(.+?) -->/g;
|
|
1879
|
+
const slugs = [];
|
|
1880
|
+
let match;
|
|
1881
|
+
while ((match = regex.exec(content)) !== null) {
|
|
1882
|
+
slugs.push(match[1]);
|
|
1883
|
+
}
|
|
1884
|
+
return slugs;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1802
1887
|
// src/lib/installers/common.ts
|
|
1803
1888
|
function safeSlugName(slug) {
|
|
1804
1889
|
return slug.replace(/\//g, "-");
|
|
@@ -1808,18 +1893,26 @@ function installFileOrSymlink(opts, targetPath) {
|
|
|
1808
1893
|
if (opts.method === "symlink") {
|
|
1809
1894
|
createSymlink(opts.cachePath, targetPath);
|
|
1810
1895
|
} else {
|
|
1811
|
-
|
|
1812
|
-
|
|
1896
|
+
mkdirSync4(join2(targetPath, ".."), { recursive: true });
|
|
1897
|
+
removeSymlink(targetPath);
|
|
1898
|
+
writeFileSync3(targetPath, opts.content);
|
|
1813
1899
|
}
|
|
1814
1900
|
return targetPath;
|
|
1815
1901
|
}
|
|
1816
1902
|
function uninstallFile(installation) {
|
|
1817
1903
|
if (installation.method === "symlink") {
|
|
1818
1904
|
removeSymlink(installation.path);
|
|
1819
|
-
} else if (
|
|
1905
|
+
} else if (existsSync4(installation.path)) {
|
|
1820
1906
|
unlinkSync2(installation.path);
|
|
1821
1907
|
}
|
|
1822
1908
|
}
|
|
1909
|
+
function installationIntact(installation, slug) {
|
|
1910
|
+
if (!existsSync4(installation.path)) return false;
|
|
1911
|
+
if (installation.method === "section") {
|
|
1912
|
+
return listSections(installation.path).includes(slug);
|
|
1913
|
+
}
|
|
1914
|
+
return true;
|
|
1915
|
+
}
|
|
1823
1916
|
function defaultTransformContent(content) {
|
|
1824
1917
|
return toPlainMD(content);
|
|
1825
1918
|
}
|
|
@@ -1837,8 +1930,8 @@ function detect(projectDir) {
|
|
|
1837
1930
|
const home = homedir2();
|
|
1838
1931
|
const cwd = projectDir || process.cwd();
|
|
1839
1932
|
return {
|
|
1840
|
-
global:
|
|
1841
|
-
project:
|
|
1933
|
+
global: existsSync5(join3(home, ".cursor")),
|
|
1934
|
+
project: existsSync5(join3(cwd, ".cursor"))
|
|
1842
1935
|
};
|
|
1843
1936
|
}
|
|
1844
1937
|
function resolvePath(slug, scope, projectDir, _contentType) {
|
|
@@ -1873,7 +1966,7 @@ var cursorAdapter = {
|
|
|
1873
1966
|
};
|
|
1874
1967
|
|
|
1875
1968
|
// src/lib/installers/claude.ts
|
|
1876
|
-
import { existsSync as
|
|
1969
|
+
import { existsSync as existsSync6, rmSync as rmSync2, statSync, readdirSync } from "fs";
|
|
1877
1970
|
import { join as join4 } from "path";
|
|
1878
1971
|
import { homedir as homedir3 } from "os";
|
|
1879
1972
|
var descriptor2 = {
|
|
@@ -1888,8 +1981,8 @@ function detect2(projectDir) {
|
|
|
1888
1981
|
const home = homedir3();
|
|
1889
1982
|
const cwd = projectDir || process.cwd();
|
|
1890
1983
|
return {
|
|
1891
|
-
global:
|
|
1892
|
-
project:
|
|
1984
|
+
global: existsSync6(join4(home, ".claude")),
|
|
1985
|
+
project: existsSync6(join4(cwd, ".claude"))
|
|
1893
1986
|
};
|
|
1894
1987
|
}
|
|
1895
1988
|
function claudeBase(scope, projectDir) {
|
|
@@ -1920,7 +2013,7 @@ function uninstall2(installation, _slug) {
|
|
|
1920
2013
|
const target = installation.path;
|
|
1921
2014
|
if (isSymlink(target)) {
|
|
1922
2015
|
removeSymlink(target);
|
|
1923
|
-
} else if (
|
|
2016
|
+
} else if (existsSync6(target)) {
|
|
1924
2017
|
if (statSync(target).isDirectory()) {
|
|
1925
2018
|
rmSync2(target, { recursive: true, force: true });
|
|
1926
2019
|
} else {
|
|
@@ -1929,7 +2022,7 @@ function uninstall2(installation, _slug) {
|
|
|
1929
2022
|
}
|
|
1930
2023
|
const parentDir = join4(target, "..");
|
|
1931
2024
|
try {
|
|
1932
|
-
if (
|
|
2025
|
+
if (existsSync6(parentDir) && readdirSync(parentDir).length === 0) {
|
|
1933
2026
|
rmSync2(parentDir, { recursive: true });
|
|
1934
2027
|
}
|
|
1935
2028
|
} catch {
|
|
@@ -1954,13 +2047,14 @@ import { join as join6 } from "path";
|
|
|
1954
2047
|
import { homedir as homedir5 } from "os";
|
|
1955
2048
|
|
|
1956
2049
|
// src/lib/detect.ts
|
|
1957
|
-
import { existsSync as
|
|
2050
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1958
2051
|
import { execFileSync } from "child_process";
|
|
1959
2052
|
import { homedir as homedir4 } from "os";
|
|
1960
2053
|
import { join as join5 } from "path";
|
|
1961
2054
|
function commandExists(cmd) {
|
|
1962
2055
|
try {
|
|
1963
|
-
|
|
2056
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
2057
|
+
execFileSync(finder, [cmd], { stdio: "ignore" });
|
|
1964
2058
|
return true;
|
|
1965
2059
|
} catch {
|
|
1966
2060
|
return false;
|
|
@@ -1970,76 +2064,21 @@ function detectInstalledPlatforms(projectDir) {
|
|
|
1970
2064
|
const detected = [];
|
|
1971
2065
|
const home = homedir4();
|
|
1972
2066
|
const cwd = projectDir || process.cwd();
|
|
1973
|
-
if (
|
|
2067
|
+
if (existsSync7(join5(home, ".cursor")) || existsSync7(join5(cwd, ".cursor")))
|
|
1974
2068
|
detected.push("cursor");
|
|
1975
|
-
if (
|
|
2069
|
+
if (existsSync7(join5(home, ".claude")) || commandExists("claude"))
|
|
1976
2070
|
detected.push("claude");
|
|
1977
2071
|
if (commandExists("codex")) detected.push("codex");
|
|
1978
|
-
if (
|
|
2072
|
+
if (existsSync7(join5(home, ".codeium")) || existsSync7(join5(cwd, ".windsurf")))
|
|
1979
2073
|
detected.push("windsurf");
|
|
1980
|
-
if (
|
|
1981
|
-
if (
|
|
1982
|
-
if (commandExists("opencode") ||
|
|
2074
|
+
if (existsSync7(join5(cwd, ".clinerules"))) detected.push("cline");
|
|
2075
|
+
if (existsSync7(join5(cwd, ".github"))) detected.push("copilot");
|
|
2076
|
+
if (commandExists("opencode") || existsSync7(join5(cwd, ".opencode")))
|
|
1983
2077
|
detected.push("opencode");
|
|
1984
2078
|
if (commandExists("aider")) detected.push("aider");
|
|
1985
2079
|
return detected;
|
|
1986
2080
|
}
|
|
1987
2081
|
|
|
1988
|
-
// src/lib/marked-sections.ts
|
|
1989
|
-
import { existsSync as existsSync7, readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
1990
|
-
import { dirname as dirname2 } from "path";
|
|
1991
|
-
var START_MARKER = (slug) => `<!-- localskills:start:${slug} -->`;
|
|
1992
|
-
var END_MARKER = (slug) => `<!-- localskills:end:${slug} -->`;
|
|
1993
|
-
function upsertSection(filePath, slug, content) {
|
|
1994
|
-
mkdirSync4(dirname2(filePath), { recursive: true });
|
|
1995
|
-
let existing = "";
|
|
1996
|
-
if (existsSync7(filePath)) {
|
|
1997
|
-
existing = readFileSync2(filePath, "utf-8");
|
|
1998
|
-
}
|
|
1999
|
-
const start = START_MARKER(slug);
|
|
2000
|
-
const end = END_MARKER(slug);
|
|
2001
|
-
const section = `${start}
|
|
2002
|
-
${content}
|
|
2003
|
-
${end}`;
|
|
2004
|
-
const startIdx = existing.indexOf(start);
|
|
2005
|
-
const endIdx = existing.indexOf(end);
|
|
2006
|
-
let result;
|
|
2007
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
2008
|
-
result = existing.slice(0, startIdx) + section + existing.slice(endIdx + end.length);
|
|
2009
|
-
} else {
|
|
2010
|
-
const separator = existing.length > 0 && !existing.endsWith("\n\n") ? "\n\n" : "";
|
|
2011
|
-
result = existing + separator + section + "\n";
|
|
2012
|
-
}
|
|
2013
|
-
writeFileSync3(filePath, result);
|
|
2014
|
-
}
|
|
2015
|
-
function removeSection(filePath, slug) {
|
|
2016
|
-
if (!existsSync7(filePath)) return false;
|
|
2017
|
-
const existing = readFileSync2(filePath, "utf-8");
|
|
2018
|
-
const start = START_MARKER(slug);
|
|
2019
|
-
const end = END_MARKER(slug);
|
|
2020
|
-
const startIdx = existing.indexOf(start);
|
|
2021
|
-
const endIdx = existing.indexOf(end);
|
|
2022
|
-
if (startIdx === -1 || endIdx === -1) return false;
|
|
2023
|
-
let before = existing.slice(0, startIdx);
|
|
2024
|
-
let after = existing.slice(endIdx + end.length);
|
|
2025
|
-
while (before.endsWith("\n\n")) before = before.slice(0, -1);
|
|
2026
|
-
while (after.startsWith("\n\n")) after = after.slice(1);
|
|
2027
|
-
const result = (before + after).trim();
|
|
2028
|
-
writeFileSync3(filePath, result ? result + "\n" : "");
|
|
2029
|
-
return true;
|
|
2030
|
-
}
|
|
2031
|
-
function listSections(filePath) {
|
|
2032
|
-
if (!existsSync7(filePath)) return [];
|
|
2033
|
-
const content = readFileSync2(filePath, "utf-8");
|
|
2034
|
-
const regex = /<!-- localskills:start:(.+?) -->/g;
|
|
2035
|
-
const slugs = [];
|
|
2036
|
-
let match;
|
|
2037
|
-
while ((match = regex.exec(content)) !== null) {
|
|
2038
|
-
slugs.push(match[1]);
|
|
2039
|
-
}
|
|
2040
|
-
return slugs;
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
2082
|
// src/lib/installers/codex.ts
|
|
2044
2083
|
var descriptor3 = {
|
|
2045
2084
|
id: "codex",
|
|
@@ -2474,6 +2513,16 @@ function getPlatformFile(slug, platform, skill) {
|
|
|
2474
2513
|
writeFileSync6(filePath, transformed);
|
|
2475
2514
|
return filePath;
|
|
2476
2515
|
}
|
|
2516
|
+
function regenerateTextPlatformFiles(slug, installations, skill) {
|
|
2517
|
+
for (const inst of installations) {
|
|
2518
|
+
if ((inst.format ?? "text") !== "text") continue;
|
|
2519
|
+
if (inst.method !== "symlink") continue;
|
|
2520
|
+
try {
|
|
2521
|
+
getPlatformFile(slug, inst.platform, skill);
|
|
2522
|
+
} catch {
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2477
2526
|
function getRawContent(slug) {
|
|
2478
2527
|
const filePath = join13(getCacheDir(slug), "raw.md");
|
|
2479
2528
|
if (!existsSync13(filePath)) return null;
|
|
@@ -2607,13 +2656,14 @@ async function interactiveTargets(detectedPlatforms) {
|
|
|
2607
2656
|
}));
|
|
2608
2657
|
return { platforms, scope, method };
|
|
2609
2658
|
}
|
|
2610
|
-
async function interactiveUninstall(
|
|
2659
|
+
async function interactiveUninstall(installedSkills) {
|
|
2611
2660
|
We("localskills uninstall");
|
|
2612
2661
|
return cancelGuard(await Je({
|
|
2613
2662
|
message: "Which skill would you like to uninstall?",
|
|
2614
|
-
options:
|
|
2615
|
-
value:
|
|
2616
|
-
label:
|
|
2663
|
+
options: Object.entries(installedSkills).map(([key, record]) => ({
|
|
2664
|
+
value: key,
|
|
2665
|
+
label: record.name || record.slug || key,
|
|
2666
|
+
hint: record.slug && record.slug !== key ? record.slug : void 0
|
|
2617
2667
|
}))
|
|
2618
2668
|
}));
|
|
2619
2669
|
}
|
|
@@ -2653,7 +2703,9 @@ function parsePlatforms(raw) {
|
|
|
2653
2703
|
}
|
|
2654
2704
|
function buildSkillRecord(cacheKey, skill, version2, resolvedSemver, requestedRange, existingInstallations, newInstallations) {
|
|
2655
2705
|
return {
|
|
2656
|
-
slug
|
|
2706
|
+
// The human slug (when known), so `uninstall`/`pull <slug>` can resolve
|
|
2707
|
+
// records that are keyed by publicId.
|
|
2708
|
+
slug: skill.slug ?? cacheKey,
|
|
2657
2709
|
name: skill.name,
|
|
2658
2710
|
type: skill.type ?? "skill",
|
|
2659
2711
|
hash: skill.contentHash,
|
|
@@ -2701,7 +2753,7 @@ var installCommand = new Command2("install").description("Install a skill locall
|
|
|
2701
2753
|
let method;
|
|
2702
2754
|
let projectDir;
|
|
2703
2755
|
if (typeof opts.project === "string") {
|
|
2704
|
-
projectDir = opts.project;
|
|
2756
|
+
projectDir = resolvePathAbs(opts.project);
|
|
2705
2757
|
}
|
|
2706
2758
|
const explicitPlatforms = parsePlatforms(opts.target);
|
|
2707
2759
|
const explicitScope = opts.global ? "global" : opts.project !== void 0 ? "project" : null;
|
|
@@ -2718,7 +2770,12 @@ var installCommand = new Command2("install").description("Install a skill locall
|
|
|
2718
2770
|
spinner2.start("Fetching available skills...");
|
|
2719
2771
|
const res2 = await client.get("/api/skills");
|
|
2720
2772
|
spinner2.stop("Fetched skills.");
|
|
2721
|
-
if (!res2.success || !res2.data
|
|
2773
|
+
if (!res2.success || !res2.data) {
|
|
2774
|
+
console.error(`Error: ${res2.error || "Failed to fetch skills."}`);
|
|
2775
|
+
process.exit(1);
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
if (res2.data.length === 0) {
|
|
2722
2779
|
console.error("No skills available.");
|
|
2723
2780
|
process.exit(1);
|
|
2724
2781
|
return;
|
|
@@ -2747,6 +2804,9 @@ var installCommand = new Command2("install").description("Install a skill locall
|
|
|
2747
2804
|
const atIdx = slug.lastIndexOf("@");
|
|
2748
2805
|
requestedRange = slug.substring(atIdx + 1);
|
|
2749
2806
|
slug = slug.substring(0, atIdx);
|
|
2807
|
+
if (requestedRange === "" || requestedRange === "latest") {
|
|
2808
|
+
requestedRange = null;
|
|
2809
|
+
}
|
|
2750
2810
|
}
|
|
2751
2811
|
const versionQuery = buildVersionQuery(requestedRange);
|
|
2752
2812
|
const spinner = bt2();
|
|
@@ -2866,6 +2926,11 @@ var installCommand = new Command2("install").description("Install a skill locall
|
|
|
2866
2926
|
const { skill, content, version: version2, semver: resolvedSemver } = resData;
|
|
2867
2927
|
spinner.stop(`Fetched ${skill.name} ${formatVersionLabel(resolvedSemver, version2)}`);
|
|
2868
2928
|
store(cacheKey, content, skill, version2);
|
|
2929
|
+
regenerateTextPlatformFiles(
|
|
2930
|
+
cacheKey,
|
|
2931
|
+
config.installed_skills[cacheKey]?.installations ?? [],
|
|
2932
|
+
skill
|
|
2933
|
+
);
|
|
2869
2934
|
const contentType = skill.type ?? "skill";
|
|
2870
2935
|
const installations = [];
|
|
2871
2936
|
const results = [];
|
|
@@ -2918,6 +2983,11 @@ var installCommand = new Command2("install").description("Install a skill locall
|
|
|
2918
2983
|
const methodLabel = actualMethod === "symlink" ? "symlinked" : actualMethod === "section" ? "section" : "copied";
|
|
2919
2984
|
results.push(`${desc.name} \u2192 ${installedPath} (${methodLabel})`);
|
|
2920
2985
|
}
|
|
2986
|
+
if (installations.length === 0) {
|
|
2987
|
+
R2.error("Nothing was installed \u2014 no selected platform supports this scope.");
|
|
2988
|
+
process.exit(1);
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2921
2991
|
config.installed_skills[cacheKey] = buildSkillRecord(
|
|
2922
2992
|
cacheKey,
|
|
2923
2993
|
skill,
|
|
@@ -2939,25 +3009,26 @@ var installCommand = new Command2("install").description("Install a skill locall
|
|
|
2939
3009
|
import { Command as Command3 } from "commander";
|
|
2940
3010
|
var uninstallCommand = new Command3("uninstall").description("Uninstall a skill").argument("[slug]", "Skill slug (omit for interactive selection)").option("--purge", "Also remove from cache").action(async (slugArg, opts) => {
|
|
2941
3011
|
const config = loadConfig();
|
|
2942
|
-
|
|
2943
|
-
if (installedSlugs.length === 0) {
|
|
3012
|
+
if (Object.keys(config.installed_skills).length === 0) {
|
|
2944
3013
|
console.log("No installed skills.");
|
|
2945
3014
|
return;
|
|
2946
3015
|
}
|
|
2947
3016
|
let slug;
|
|
2948
3017
|
if (slugArg) {
|
|
2949
|
-
|
|
3018
|
+
const resolved = resolveInstalledSkillKey(config, slugArg);
|
|
3019
|
+
if (!resolved) {
|
|
3020
|
+
console.error(`Skill "${slugArg}" is not installed.`);
|
|
3021
|
+
process.exit(1);
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
slug = resolved;
|
|
2950
3025
|
} else {
|
|
2951
|
-
slug = await interactiveUninstall(
|
|
3026
|
+
slug = await interactiveUninstall(config.installed_skills);
|
|
2952
3027
|
}
|
|
2953
3028
|
const installed = config.installed_skills[slug];
|
|
2954
|
-
if (!installed) {
|
|
2955
|
-
console.error(`Skill "${slug}" is not installed.`);
|
|
2956
|
-
process.exit(1);
|
|
2957
|
-
return;
|
|
2958
|
-
}
|
|
2959
3029
|
We(`localskills uninstall ${slug}`);
|
|
2960
3030
|
let removed = 0;
|
|
3031
|
+
const failed = [];
|
|
2961
3032
|
for (const installation of installed.installations) {
|
|
2962
3033
|
try {
|
|
2963
3034
|
const adapter = getAdapter(installation.platform);
|
|
@@ -2970,13 +3041,26 @@ var uninstallCommand = new Command3("uninstall").description("Uninstall a skill"
|
|
|
2970
3041
|
R2.warn(
|
|
2971
3042
|
`${installation.platform} \u2014 failed to remove: ${err instanceof Error ? err.message : String(err)}`
|
|
2972
3043
|
);
|
|
3044
|
+
failed.push(installation);
|
|
2973
3045
|
}
|
|
2974
3046
|
}
|
|
2975
|
-
|
|
3047
|
+
if (failed.length === 0) {
|
|
3048
|
+
delete config.installed_skills[slug];
|
|
3049
|
+
} else {
|
|
3050
|
+
installed.installations = failed;
|
|
3051
|
+
R2.warn(
|
|
3052
|
+
`${failed.length} target(s) could not be removed and remain tracked. Fix the issue and run \`localskills uninstall ${slug}\` again.`
|
|
3053
|
+
);
|
|
3054
|
+
process.exitCode = 1;
|
|
3055
|
+
}
|
|
2976
3056
|
saveConfig(config);
|
|
2977
3057
|
if (opts?.purge) {
|
|
2978
|
-
|
|
2979
|
-
|
|
3058
|
+
if (failed.length === 0) {
|
|
3059
|
+
purge(slug);
|
|
3060
|
+
R2.info("Cache purged.");
|
|
3061
|
+
} else {
|
|
3062
|
+
R2.warn("Skipped cache purge because some targets could not be removed.");
|
|
3063
|
+
}
|
|
2980
3064
|
}
|
|
2981
3065
|
Le(
|
|
2982
3066
|
`Uninstalled ${slug} from ${removed} target(s).`
|
|
@@ -3079,7 +3163,18 @@ function installedAsPackage(inst) {
|
|
|
3079
3163
|
}
|
|
3080
3164
|
var pullCommand = new Command5("pull").description("Pull latest versions of all installed skills").argument("[slug]", "Pull a specific skill (omit for all)").action(async (slugArg) => {
|
|
3081
3165
|
const config = loadConfig();
|
|
3082
|
-
|
|
3166
|
+
let slugs;
|
|
3167
|
+
if (slugArg) {
|
|
3168
|
+
const resolved = resolveInstalledSkillKey(config, slugArg);
|
|
3169
|
+
if (!resolved) {
|
|
3170
|
+
console.error(`Skill "${slugArg}" is not installed.`);
|
|
3171
|
+
process.exit(1);
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
slugs = [resolved];
|
|
3175
|
+
} else {
|
|
3176
|
+
slugs = Object.keys(config.installed_skills);
|
|
3177
|
+
}
|
|
3083
3178
|
if (slugs.length === 0) {
|
|
3084
3179
|
console.log("No installed skills. Use `localskills install` first.");
|
|
3085
3180
|
return;
|
|
@@ -3108,9 +3203,16 @@ var pullCommand = new Command5("pull").description("Pull latest versions of all
|
|
|
3108
3203
|
const format = resData.format ?? "text";
|
|
3109
3204
|
const { skill, version: version2 } = resData;
|
|
3110
3205
|
if (skill.contentHash === installed.hash) {
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3206
|
+
const broken = installed.installations.some(
|
|
3207
|
+
(inst) => !installationIntact(inst, slug)
|
|
3208
|
+
);
|
|
3209
|
+
if (!broken) {
|
|
3210
|
+
spinner.stop(`${slug} \u2014 up to date`);
|
|
3211
|
+
skipped++;
|
|
3212
|
+
continue;
|
|
3213
|
+
}
|
|
3214
|
+
spinner.stop(`${slug} \u2014 content unchanged but installation broken, repairing...`);
|
|
3215
|
+
spinner.start(`Repairing ${slug}...`);
|
|
3114
3216
|
}
|
|
3115
3217
|
let allHandled = true;
|
|
3116
3218
|
if (format === "package") {
|
|
@@ -3193,7 +3295,17 @@ var pullCommand = new Command5("pull").description("Pull latest versions of all
|
|
|
3193
3295
|
continue;
|
|
3194
3296
|
}
|
|
3195
3297
|
if (installation.method === "symlink") {
|
|
3196
|
-
getPlatformFile(slug, installation.platform, skill);
|
|
3298
|
+
const cachePath = getPlatformFile(slug, installation.platform, skill);
|
|
3299
|
+
const installedPath = adapter.install({
|
|
3300
|
+
slug,
|
|
3301
|
+
content: transformed,
|
|
3302
|
+
scope: installation.scope,
|
|
3303
|
+
method: "symlink",
|
|
3304
|
+
cachePath,
|
|
3305
|
+
projectDir: installation.projectDir,
|
|
3306
|
+
contentType: skill.type
|
|
3307
|
+
});
|
|
3308
|
+
installation.path = installedPath;
|
|
3197
3309
|
installation.format = "text";
|
|
3198
3310
|
continue;
|
|
3199
3311
|
}
|
|
@@ -3614,7 +3726,12 @@ var publishCommand = new Command6("publish").description("Publish local skills t
|
|
|
3614
3726
|
const client = new ApiClient();
|
|
3615
3727
|
requireAuth(client);
|
|
3616
3728
|
const teamsRes = await client.get("/api/tenants");
|
|
3617
|
-
if (!teamsRes.success || !teamsRes.data
|
|
3729
|
+
if (!teamsRes.success || !teamsRes.data) {
|
|
3730
|
+
console.error(`Error: ${teamsRes.error || "Failed to fetch your teams."}`);
|
|
3731
|
+
process.exit(1);
|
|
3732
|
+
return;
|
|
3733
|
+
}
|
|
3734
|
+
if (teamsRes.data.length === 0) {
|
|
3618
3735
|
console.error(
|
|
3619
3736
|
"No teams found. Create a team at localskills.sh first."
|
|
3620
3737
|
);
|
|
@@ -3940,6 +4057,7 @@ var shareCommand = new Command8("share").description("Share a skill anonymously
|
|
|
3940
4057
|
}
|
|
3941
4058
|
await ensureAnonymousIdentity();
|
|
3942
4059
|
const client = new ApiClient();
|
|
4060
|
+
const tenantId = await resolveShareTenant(client);
|
|
3943
4061
|
if (fileArg) {
|
|
3944
4062
|
const filePath = resolve6(fileArg);
|
|
3945
4063
|
if (!existsSync17(filePath)) {
|
|
@@ -3952,7 +4070,8 @@ var shareCommand = new Command8("share").description("Share a skill anonymously
|
|
|
3952
4070
|
const ok2 = await uploadAnonymousPackage(client, {
|
|
3953
4071
|
name: skillName2,
|
|
3954
4072
|
dir: filePath,
|
|
3955
|
-
type: contentType2
|
|
4073
|
+
type: contentType2,
|
|
4074
|
+
tenantId
|
|
3956
4075
|
});
|
|
3957
4076
|
Le(ok2 ? "Done!" : "Share failed.");
|
|
3958
4077
|
if (!ok2) process.exit(1);
|
|
@@ -3968,7 +4087,7 @@ var shareCommand = new Command8("share").description("Share a skill anonymously
|
|
|
3968
4087
|
const defaultName = titleFromSlug(defaultSlug);
|
|
3969
4088
|
const skillName = opts.name || defaultName;
|
|
3970
4089
|
const contentType = opts.type === "rule" ? "rule" : "skill";
|
|
3971
|
-
const ok = await uploadAnonymousSkill(client, { name: skillName, content, type: contentType });
|
|
4090
|
+
const ok = await uploadAnonymousSkill(client, { name: skillName, content, type: contentType, tenantId });
|
|
3972
4091
|
Le(ok ? "Done!" : "Share failed.");
|
|
3973
4092
|
if (!ok) process.exit(1);
|
|
3974
4093
|
return;
|
|
@@ -4008,11 +4127,13 @@ var shareCommand = new Command8("share").description("Share a skill anonymously
|
|
|
4008
4127
|
const ok = selected.format === "package" && selected.dir ? await uploadAnonymousPackage(client, {
|
|
4009
4128
|
name,
|
|
4010
4129
|
dir: selected.dir,
|
|
4011
|
-
type: selected.contentType
|
|
4130
|
+
type: selected.contentType,
|
|
4131
|
+
tenantId
|
|
4012
4132
|
}) : await uploadAnonymousSkill(client, {
|
|
4013
4133
|
name,
|
|
4014
4134
|
content: selected.content,
|
|
4015
|
-
type: selected.contentType
|
|
4135
|
+
type: selected.contentType,
|
|
4136
|
+
tenantId
|
|
4016
4137
|
});
|
|
4017
4138
|
Le(ok ? "Done!" : "Share failed.");
|
|
4018
4139
|
if (!ok) process.exit(1);
|
|
@@ -4060,19 +4181,36 @@ async function ensureAnonymousIdentity() {
|
|
|
4060
4181
|
setToken(res.data.token);
|
|
4061
4182
|
s.stop(`Connected as ${res.data.username}`);
|
|
4062
4183
|
}
|
|
4063
|
-
async function
|
|
4184
|
+
async function resolveShareTenant(client) {
|
|
4064
4185
|
const s = bt2();
|
|
4065
|
-
s.start(
|
|
4186
|
+
s.start("Looking up your team...");
|
|
4066
4187
|
const teamsRes = await client.get("/api/tenants");
|
|
4067
4188
|
if (!teamsRes.success || !teamsRes.data || teamsRes.data.length === 0) {
|
|
4068
|
-
s.stop(
|
|
4189
|
+
s.stop(
|
|
4190
|
+
`Failed to find your team: ${teamsRes.error || "no teams"}. Try running \`localskills share\` again.`
|
|
4191
|
+
);
|
|
4069
4192
|
process.exit(1);
|
|
4070
4193
|
}
|
|
4071
|
-
const
|
|
4194
|
+
const teams = teamsRes.data;
|
|
4195
|
+
if (teams.length === 1) {
|
|
4196
|
+
s.stop("Team found.");
|
|
4197
|
+
return teams[0].id;
|
|
4198
|
+
}
|
|
4199
|
+
s.stop(`You belong to ${teams.length} teams.`);
|
|
4200
|
+
return cancelGuard(
|
|
4201
|
+
await Je({
|
|
4202
|
+
message: "Share under which team?",
|
|
4203
|
+
options: teams.map((t) => ({ value: t.id, label: t.name, hint: t.slug }))
|
|
4204
|
+
})
|
|
4205
|
+
);
|
|
4206
|
+
}
|
|
4207
|
+
async function uploadAnonymousSkill(client, params) {
|
|
4208
|
+
const s = bt2();
|
|
4209
|
+
s.start(`Sharing "${params.name}"...`);
|
|
4072
4210
|
const res = await client.post("/api/skills", {
|
|
4073
4211
|
name: params.name,
|
|
4074
4212
|
content: params.content,
|
|
4075
|
-
tenantId,
|
|
4213
|
+
tenantId: params.tenantId,
|
|
4076
4214
|
visibility: "unlisted",
|
|
4077
4215
|
type: params.type
|
|
4078
4216
|
});
|
|
@@ -4096,16 +4234,9 @@ async function uploadAnonymousPackage(client, params) {
|
|
|
4096
4234
|
}
|
|
4097
4235
|
for (const w of packed.warnings) R2.warn(w);
|
|
4098
4236
|
s.start(`Sharing "${params.name}" (${packed.fileCount} files)...`);
|
|
4099
|
-
const teamsRes = await client.get("/api/tenants");
|
|
4100
|
-
if (!teamsRes.success || !teamsRes.data || teamsRes.data.length === 0) {
|
|
4101
|
-
s.stop("Failed to find your team. Try running `localskills share` again.");
|
|
4102
|
-
process.exit(1);
|
|
4103
|
-
return false;
|
|
4104
|
-
}
|
|
4105
|
-
const tenantId = teamsRes.data[0].id;
|
|
4106
4237
|
const form = new FormData();
|
|
4107
4238
|
form.append("name", params.name);
|
|
4108
|
-
form.append("tenantId", tenantId);
|
|
4239
|
+
form.append("tenantId", params.tenantId);
|
|
4109
4240
|
form.append("visibility", "unlisted");
|
|
4110
4241
|
form.append("type", params.type);
|
|
4111
4242
|
form.append("file", zipBlob(packed.zip), "skill.zip");
|