ctx7 0.2.3 → 0.2.4-canary.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/dist/index.js +349 -27
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -8,8 +8,8 @@ import figlet from "figlet";
|
|
|
8
8
|
// src/commands/skill.ts
|
|
9
9
|
import pc7 from "picocolors";
|
|
10
10
|
import ora3 from "ora";
|
|
11
|
-
import { readdir, rm as
|
|
12
|
-
import { join as
|
|
11
|
+
import { readdir as readdir2, rm as rm3 } from "fs/promises";
|
|
12
|
+
import { join as join7 } from "path";
|
|
13
13
|
|
|
14
14
|
// src/utils/parse-input.ts
|
|
15
15
|
function parseSkillInput(input2) {
|
|
@@ -1495,7 +1495,7 @@ async function generateCommand(options) {
|
|
|
1495
1495
|
import { homedir as homedir4 } from "os";
|
|
1496
1496
|
|
|
1497
1497
|
// src/utils/deps.ts
|
|
1498
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
1498
|
+
import { readFile as readFile2, readdir } from "fs/promises";
|
|
1499
1499
|
import { join as join5 } from "path";
|
|
1500
1500
|
async function readFileOrNull(path2) {
|
|
1501
1501
|
try {
|
|
@@ -1513,11 +1513,16 @@ async function parsePackageJson(cwd) {
|
|
|
1513
1513
|
try {
|
|
1514
1514
|
const pkg2 = JSON.parse(content);
|
|
1515
1515
|
const names = /* @__PURE__ */ new Set();
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1516
|
+
const depTypes = [
|
|
1517
|
+
"dependencies",
|
|
1518
|
+
"devDependencies",
|
|
1519
|
+
"optionalDependencies",
|
|
1520
|
+
"peerDependencies"
|
|
1521
|
+
];
|
|
1522
|
+
for (const type of depTypes) {
|
|
1523
|
+
for (const key of Object.keys(pkg2[type] || {})) {
|
|
1524
|
+
if (!isSkippedLocally(key)) names.add(key);
|
|
1525
|
+
}
|
|
1521
1526
|
}
|
|
1522
1527
|
return [...names];
|
|
1523
1528
|
} catch {
|
|
@@ -1532,9 +1537,7 @@ async function parseRequirementsTxt(cwd) {
|
|
|
1532
1537
|
const trimmed = line.trim();
|
|
1533
1538
|
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
|
|
1534
1539
|
const name = trimmed.split(/[=<>!~;@\s\[]/)[0].trim();
|
|
1535
|
-
if (name && !isSkippedLocally(name))
|
|
1536
|
-
deps.push(name);
|
|
1537
|
-
}
|
|
1540
|
+
if (name && !isSkippedLocally(name)) deps.push(name);
|
|
1538
1541
|
}
|
|
1539
1542
|
return deps;
|
|
1540
1543
|
}
|
|
@@ -1556,8 +1559,7 @@ async function parsePyprojectToml(cwd) {
|
|
|
1556
1559
|
}
|
|
1557
1560
|
const poetryMatch = content.match(/\[tool\.poetry\.dependencies\]([\s\S]*?)(?:\n\[|$)/);
|
|
1558
1561
|
if (poetryMatch) {
|
|
1559
|
-
const
|
|
1560
|
-
for (const line of lines) {
|
|
1562
|
+
for (const line of poetryMatch[1].split("\n")) {
|
|
1561
1563
|
const match = line.match(/^(\S+)\s*=/);
|
|
1562
1564
|
if (match) {
|
|
1563
1565
|
const name = match[1].trim();
|
|
@@ -1578,6 +1580,109 @@ async function detectProjectDependencies(cwd) {
|
|
|
1578
1580
|
]);
|
|
1579
1581
|
return [...new Set(results.flat())];
|
|
1580
1582
|
}
|
|
1583
|
+
async function parseLockfileDeps(cwd) {
|
|
1584
|
+
const content = await readFileOrNull(join5(cwd, "package-lock.json"));
|
|
1585
|
+
if (!content) return null;
|
|
1586
|
+
try {
|
|
1587
|
+
const lock = JSON.parse(content);
|
|
1588
|
+
const rootPkg = lock.packages?.[""];
|
|
1589
|
+
if (rootPkg) {
|
|
1590
|
+
const deps = [
|
|
1591
|
+
...Object.keys(rootPkg.dependencies || {}),
|
|
1592
|
+
...Object.keys(rootPkg.devDependencies || {}),
|
|
1593
|
+
...Object.keys(rootPkg.optionalDependencies || {})
|
|
1594
|
+
];
|
|
1595
|
+
return deps.filter((d) => !isSkippedLocally(d));
|
|
1596
|
+
}
|
|
1597
|
+
} catch {
|
|
1598
|
+
}
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
async function detectNewlyInstalledPackages(cwd) {
|
|
1602
|
+
const declaredDeps = await parsePackageJson(cwd);
|
|
1603
|
+
const declaredSet = new Set(declaredDeps);
|
|
1604
|
+
const nodeModulesPath = join5(cwd, "node_modules");
|
|
1605
|
+
try {
|
|
1606
|
+
const entries = await readdir(nodeModulesPath);
|
|
1607
|
+
const isPnpm = entries.includes(".pnpm");
|
|
1608
|
+
if (isPnpm) {
|
|
1609
|
+
const regularPackages = entries.filter((e) => !e.startsWith(".") && !e.startsWith("@"));
|
|
1610
|
+
const scopedPackages = [];
|
|
1611
|
+
for (const scope of entries.filter((e) => e.startsWith("@"))) {
|
|
1612
|
+
try {
|
|
1613
|
+
const packages = await readdir(join5(nodeModulesPath, scope));
|
|
1614
|
+
for (const pkg2 of packages) {
|
|
1615
|
+
if (!pkg2.startsWith(".")) scopedPackages.push(`${scope}/${pkg2}`);
|
|
1616
|
+
}
|
|
1617
|
+
} catch {
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
const installed = [...regularPackages, ...scopedPackages];
|
|
1621
|
+
return installed.filter((pkg2) => !declaredSet.has(pkg2) && !isSkippedLocally(pkg2));
|
|
1622
|
+
} else {
|
|
1623
|
+
const lockfileDeps = await parseLockfileDeps(cwd);
|
|
1624
|
+
return lockfileDeps ? lockfileDeps.filter((pkg2) => !declaredSet.has(pkg2)) : [];
|
|
1625
|
+
}
|
|
1626
|
+
} catch {
|
|
1627
|
+
return [];
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// src/utils/cache.ts
|
|
1632
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
1633
|
+
import { join as join6 } from "path";
|
|
1634
|
+
var CACHE_DIR = join6("node_modules", ".cache", "context7");
|
|
1635
|
+
var PENDING_FILE = "pending.json";
|
|
1636
|
+
var PENDING_TTL_MS = 20 * 60 * 1e3;
|
|
1637
|
+
async function getCachePath(cwd) {
|
|
1638
|
+
const dir = join6(cwd, CACHE_DIR);
|
|
1639
|
+
await mkdir3(dir, { recursive: true });
|
|
1640
|
+
return join6(dir, PENDING_FILE);
|
|
1641
|
+
}
|
|
1642
|
+
async function loadPendingPackages(cwd) {
|
|
1643
|
+
try {
|
|
1644
|
+
const cachePath = await getCachePath(cwd);
|
|
1645
|
+
const content = await readFile3(cachePath, "utf-8");
|
|
1646
|
+
const cache = JSON.parse(content);
|
|
1647
|
+
const now = Date.now();
|
|
1648
|
+
const valid = cache.packages.filter((p) => now - p.detectedAt < PENDING_TTL_MS);
|
|
1649
|
+
return valid.map((p) => p.name);
|
|
1650
|
+
} catch {
|
|
1651
|
+
return [];
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
async function savePendingPackages(cwd, packages) {
|
|
1655
|
+
const cachePath = await getCachePath(cwd);
|
|
1656
|
+
const cache = {
|
|
1657
|
+
packages: packages.map((name) => ({
|
|
1658
|
+
name,
|
|
1659
|
+
detectedAt: Date.now()
|
|
1660
|
+
}))
|
|
1661
|
+
};
|
|
1662
|
+
await writeFile3(cachePath, JSON.stringify(cache, null, 2));
|
|
1663
|
+
}
|
|
1664
|
+
async function clearPendingPackages(cwd) {
|
|
1665
|
+
try {
|
|
1666
|
+
const cachePath = await getCachePath(cwd);
|
|
1667
|
+
await rm2(cachePath, { force: true });
|
|
1668
|
+
} catch {
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
async function appendPendingPackages(cwd, newPackages) {
|
|
1672
|
+
const existing = await loadPendingPackages(cwd);
|
|
1673
|
+
const merged = [.../* @__PURE__ */ new Set([...existing, ...newPackages])];
|
|
1674
|
+
await savePendingPackages(cwd, merged);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// src/utils/ansi.ts
|
|
1678
|
+
var ansi = {
|
|
1679
|
+
reset: "\x1B[0m",
|
|
1680
|
+
bold: "\x1B[1m",
|
|
1681
|
+
dim: "\x1B[2m",
|
|
1682
|
+
cyan: "\x1B[36m",
|
|
1683
|
+
green: "\x1B[32m",
|
|
1684
|
+
yellow: "\x1B[33m"
|
|
1685
|
+
};
|
|
1581
1686
|
|
|
1582
1687
|
// src/commands/skill.ts
|
|
1583
1688
|
function logInstallSummary(targets, targetDirs, skillNames) {
|
|
@@ -1612,9 +1717,12 @@ function registerSkillCommands(program2) {
|
|
|
1612
1717
|
skill.command("info").argument("<repository>", "GitHub repository (/owner/repo)").description("Show skills in a repository").action(async (project) => {
|
|
1613
1718
|
await infoCommand(project);
|
|
1614
1719
|
});
|
|
1615
|
-
skill.command("suggest").option("--global", "Install globally instead of current directory").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--codex", "Codex (.codex/skills/)").option("--opencode", "OpenCode (.opencode/skills/)").option("--amp", "Amp (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("Suggest skills based on your project dependencies").action(async (options) => {
|
|
1720
|
+
skill.command("suggest").option("--global", "Install globally instead of current directory").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--codex", "Codex (.codex/skills/)").option("--opencode", "OpenCode (.opencode/skills/)").option("--amp", "Amp (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("Suggest and install skills based on your project dependencies").action(async (options) => {
|
|
1616
1721
|
await suggestCommand(options);
|
|
1617
1722
|
});
|
|
1723
|
+
skill.command("detect").description("Detect newly installed packages (for postinstall hooks)").action(async () => {
|
|
1724
|
+
await detectCommand();
|
|
1725
|
+
});
|
|
1618
1726
|
}
|
|
1619
1727
|
function registerSkillAliases(program2) {
|
|
1620
1728
|
program2.command("si", { hidden: true }).argument("<repository>", "GitHub repository (/owner/repo)").argument("[skill]", "Specific skill name to install").option("--all", "Install all skills without prompting").option("--global", "Install globally instead of current directory").option("--claude", "Claude Code (.claude/skills/)").option("--cursor", "Cursor (.cursor/skills/)").option("--codex", "Codex (.codex/skills/)").option("--opencode", "OpenCode (.opencode/skills/)").option("--amp", "Amp (.agents/skills/)").option("--antigravity", "Antigravity (.agent/skills/)").description("Install skills (alias for: skills install)").action(async (project, skillName, options) => {
|
|
@@ -1757,7 +1865,7 @@ async function installCommand(input2, skillName, options) {
|
|
|
1757
1865
|
}
|
|
1758
1866
|
throw dirErr;
|
|
1759
1867
|
}
|
|
1760
|
-
const primarySkillDir =
|
|
1868
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
1761
1869
|
for (const targetDir of symlinkDirs) {
|
|
1762
1870
|
try {
|
|
1763
1871
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -1785,7 +1893,7 @@ async function installCommand(input2, skillName, options) {
|
|
|
1785
1893
|
log.blank();
|
|
1786
1894
|
log.warn("Fix permissions with:");
|
|
1787
1895
|
for (const dir of failedDirs) {
|
|
1788
|
-
const parentDir =
|
|
1896
|
+
const parentDir = join7(dir, "..");
|
|
1789
1897
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
1790
1898
|
}
|
|
1791
1899
|
log.blank();
|
|
@@ -1898,7 +2006,7 @@ async function searchCommand(query) {
|
|
|
1898
2006
|
}
|
|
1899
2007
|
throw dirErr;
|
|
1900
2008
|
}
|
|
1901
|
-
const primarySkillDir =
|
|
2009
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
1902
2010
|
for (const targetDir of symlinkDirs) {
|
|
1903
2011
|
try {
|
|
1904
2012
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -1926,7 +2034,7 @@ async function searchCommand(query) {
|
|
|
1926
2034
|
log.blank();
|
|
1927
2035
|
log.warn("Fix permissions with:");
|
|
1928
2036
|
for (const dir of failedDirs) {
|
|
1929
|
-
const parentDir =
|
|
2037
|
+
const parentDir = join7(dir, "..");
|
|
1930
2038
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
1931
2039
|
}
|
|
1932
2040
|
log.blank();
|
|
@@ -1945,9 +2053,9 @@ async function listCommand(options) {
|
|
|
1945
2053
|
const idesToCheck = hasExplicitIdeOption(options) ? getSelectedIdes(options) : Object.keys(IDE_NAMES);
|
|
1946
2054
|
const results = [];
|
|
1947
2055
|
for (const ide of idesToCheck) {
|
|
1948
|
-
const skillsDir =
|
|
2056
|
+
const skillsDir = join7(baseDir, pathMap[ide]);
|
|
1949
2057
|
try {
|
|
1950
|
-
const entries = await
|
|
2058
|
+
const entries = await readdir2(skillsDir, { withFileTypes: true });
|
|
1951
2059
|
const skillFolders = entries.filter((e) => e.isDirectory() || e.isSymbolicLink()).map((e) => e.name);
|
|
1952
2060
|
if (skillFolders.length > 0) {
|
|
1953
2061
|
results.push({ ide, skills: skillFolders });
|
|
@@ -1978,9 +2086,9 @@ async function removeCommand(name, options) {
|
|
|
1978
2086
|
return;
|
|
1979
2087
|
}
|
|
1980
2088
|
const skillsDir = getTargetDirFromSelection(target.ide, target.scope);
|
|
1981
|
-
const skillPath =
|
|
2089
|
+
const skillPath = join7(skillsDir, name);
|
|
1982
2090
|
try {
|
|
1983
|
-
await
|
|
2091
|
+
await rm3(skillPath, { recursive: true });
|
|
1984
2092
|
log.success(`Removed skill: ${name}`);
|
|
1985
2093
|
} catch (err) {
|
|
1986
2094
|
const error = err;
|
|
@@ -2030,11 +2138,225 @@ async function infoCommand(input2) {
|
|
|
2030
2138
|
`
|
|
2031
2139
|
);
|
|
2032
2140
|
}
|
|
2141
|
+
async function detectCommand() {
|
|
2142
|
+
if (process.env.CI || process.env.NODE_ENV === "production") {
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
trackEvent("command", { name: "detect" });
|
|
2146
|
+
const cwd = process.cwd();
|
|
2147
|
+
const isInteractive = process.stdout.isTTY;
|
|
2148
|
+
let deps;
|
|
2149
|
+
if (isInteractive) {
|
|
2150
|
+
const pendingPackages = await loadPendingPackages(cwd);
|
|
2151
|
+
if (pendingPackages.length > 0) {
|
|
2152
|
+
deps = pendingPackages;
|
|
2153
|
+
log.blank();
|
|
2154
|
+
log.info(
|
|
2155
|
+
`Found ${pc7.bold(String(deps.length))} pending package(s) from recent installs: ${pc7.cyan(deps.join(", "))}`
|
|
2156
|
+
);
|
|
2157
|
+
} else {
|
|
2158
|
+
const scanSpinner = ora3("Detecting newly installed packages...").start();
|
|
2159
|
+
deps = await detectNewlyInstalledPackages(cwd);
|
|
2160
|
+
if (deps.length === 0) {
|
|
2161
|
+
scanSpinner.stop();
|
|
2162
|
+
scanSpinner.clear();
|
|
2163
|
+
log.blank();
|
|
2164
|
+
log.warn("No new packages detected");
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
await appendPendingPackages(cwd, deps);
|
|
2168
|
+
scanSpinner.succeed(
|
|
2169
|
+
`Detected ${deps.length} new package(s): ${ansi.yellow}${deps.join(", ")}${ansi.reset}`
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
} else {
|
|
2173
|
+
const scanSpinner = ora3("Detecting newly installed packages...").start();
|
|
2174
|
+
deps = await detectNewlyInstalledPackages(cwd);
|
|
2175
|
+
if (deps.length === 0) {
|
|
2176
|
+
scanSpinner.stop();
|
|
2177
|
+
scanSpinner.clear();
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
await appendPendingPackages(cwd, deps);
|
|
2181
|
+
scanSpinner.succeed(
|
|
2182
|
+
`Detected ${deps.length} new package(s): ${ansi.yellow}${deps.join(", ")}${ansi.reset}`
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
const searchSpinner = ora3("Finding matching skills...").start();
|
|
2186
|
+
const tokens = loadTokens();
|
|
2187
|
+
const accessToken = tokens && !isTokenExpired(tokens) ? tokens.access_token : void 0;
|
|
2188
|
+
let data;
|
|
2189
|
+
try {
|
|
2190
|
+
data = await suggestSkills(deps, accessToken);
|
|
2191
|
+
} catch {
|
|
2192
|
+
searchSpinner.fail(pc7.red("Failed to connect to Context7"));
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
if (data.error) {
|
|
2196
|
+
searchSpinner.fail(pc7.red(`Error: ${data.message || data.error}`));
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const skills = data.skills;
|
|
2200
|
+
if (skills.length === 0) {
|
|
2201
|
+
searchSpinner.stop();
|
|
2202
|
+
searchSpinner.clear();
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
searchSpinner.succeed(`Found ${skills.length} relevant skill(s)`);
|
|
2206
|
+
if (!isInteractive) {
|
|
2207
|
+
console.log();
|
|
2208
|
+
console.log(
|
|
2209
|
+
`${ansi.cyan}${ansi.bold}${skills.length}${ansi.reset} skill(s) available for new packages:`
|
|
2210
|
+
);
|
|
2211
|
+
for (const skill of skills.slice(0, 5)) {
|
|
2212
|
+
console.log(
|
|
2213
|
+
`${ansi.dim}\u2502${ansi.reset} ${ansi.green}+${ansi.reset} ${ansi.bold}${ansi.green}${skill.name}${ansi.reset} ${ansi.dim}for${ansi.reset} ${ansi.yellow}${skill.matchedDep}${ansi.reset}`
|
|
2214
|
+
);
|
|
2215
|
+
if (skill.description) {
|
|
2216
|
+
const desc = skill.description.length > 55 ? skill.description.slice(0, 52) + "..." : skill.description;
|
|
2217
|
+
console.log(`${ansi.dim}\u2502 ${desc}${ansi.reset}`);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
if (skills.length > 5) {
|
|
2221
|
+
console.log(`${ansi.dim}\u2502 ... and ${skills.length - 5} more${ansi.reset}`);
|
|
2222
|
+
}
|
|
2223
|
+
console.log(
|
|
2224
|
+
`${ansi.dim}\u2514${ansi.reset} Run ${ansi.cyan}${ansi.bold}ctx7 skills detect${ansi.reset} to install`
|
|
2225
|
+
);
|
|
2226
|
+
console.log();
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
log.blank();
|
|
2230
|
+
const maxNameLen = Math.max(...skills.map((s) => s.name.length));
|
|
2231
|
+
const installsColWidth = 10;
|
|
2232
|
+
const trustColWidth = 12;
|
|
2233
|
+
const maxMatchedLen = Math.max(...skills.map((s) => s.matchedDep.length));
|
|
2234
|
+
const indexWidth = skills.length.toString().length;
|
|
2235
|
+
const choices = skills.map((s, index) => {
|
|
2236
|
+
const indexStr = pc7.dim(`${(index + 1).toString().padStart(indexWidth)}.`);
|
|
2237
|
+
const paddedName = s.name.padEnd(maxNameLen);
|
|
2238
|
+
const installsRaw = s.installCount ? String(s.installCount) : "-";
|
|
2239
|
+
const paddedInstalls = formatInstallCount(s.installCount, pc7.dim("-")) + " ".repeat(installsColWidth - installsRaw.length);
|
|
2240
|
+
const trustRaw = s.trustScore !== void 0 && s.trustScore >= 0 ? s.trustScore.toFixed(1) : "-";
|
|
2241
|
+
const trust = formatTrustScore(s.trustScore) + " ".repeat(trustColWidth - trustRaw.length);
|
|
2242
|
+
const matched = pc7.yellow(s.matchedDep.padEnd(maxMatchedLen));
|
|
2243
|
+
const skillLink = terminalLink(
|
|
2244
|
+
s.name,
|
|
2245
|
+
`https://context7.com/skills${s.project}/${s.name}`,
|
|
2246
|
+
pc7.white
|
|
2247
|
+
);
|
|
2248
|
+
const repoLink = terminalLink(s.project, `https://github.com${s.project}`, pc7.white);
|
|
2249
|
+
const metadataLines = [
|
|
2250
|
+
pc7.dim("\u2500".repeat(50)),
|
|
2251
|
+
"",
|
|
2252
|
+
`${pc7.yellow("Skill:")} ${skillLink}`,
|
|
2253
|
+
`${pc7.yellow("Repo:")} ${repoLink}`,
|
|
2254
|
+
`${pc7.yellow("Relevant:")} ${pc7.white(s.matchedDep)}`,
|
|
2255
|
+
`${pc7.yellow("Description:")}`,
|
|
2256
|
+
pc7.white(s.description || "No description")
|
|
2257
|
+
];
|
|
2258
|
+
return {
|
|
2259
|
+
name: `${indexStr} ${paddedName} ${paddedInstalls}${trust}${matched}`,
|
|
2260
|
+
value: s,
|
|
2261
|
+
description: metadataLines.join("\n")
|
|
2262
|
+
};
|
|
2263
|
+
});
|
|
2264
|
+
const checkboxPrefixWidth = 3;
|
|
2265
|
+
const headerPad = " ".repeat(checkboxPrefixWidth + indexWidth + 1 + 1 + maxNameLen + 1);
|
|
2266
|
+
const headerLine = headerPad + pc7.dim("Installs".padEnd(installsColWidth)) + pc7.dim("Trust(0-10)".padEnd(trustColWidth)) + pc7.dim("Relevant");
|
|
2267
|
+
const message = "Select skills to install:\n" + headerLine;
|
|
2268
|
+
let selectedSkills;
|
|
2269
|
+
try {
|
|
2270
|
+
selectedSkills = await checkboxWithHover({
|
|
2271
|
+
message,
|
|
2272
|
+
choices,
|
|
2273
|
+
pageSize: 15,
|
|
2274
|
+
loop: false
|
|
2275
|
+
});
|
|
2276
|
+
} catch {
|
|
2277
|
+
log.warn("Installation cancelled");
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
if (selectedSkills.length === 0) {
|
|
2281
|
+
log.warn("No skills selected");
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
const targets = await promptForInstallTargets({});
|
|
2285
|
+
if (!targets) {
|
|
2286
|
+
log.warn("Installation cancelled");
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
const targetDirs = getTargetDirs(targets);
|
|
2290
|
+
const installSpinner = ora3("Installing skills...").start();
|
|
2291
|
+
let permissionError = false;
|
|
2292
|
+
const failedDirs = /* @__PURE__ */ new Set();
|
|
2293
|
+
const installedSkills = [];
|
|
2294
|
+
for (const skill of selectedSkills) {
|
|
2295
|
+
try {
|
|
2296
|
+
installSpinner.text = `Downloading ${skill.name}...`;
|
|
2297
|
+
const downloadData = await downloadSkill(skill.project, skill.name);
|
|
2298
|
+
if (downloadData.error) {
|
|
2299
|
+
log.warn(`Failed to download ${skill.name}: ${downloadData.error}`);
|
|
2300
|
+
continue;
|
|
2301
|
+
}
|
|
2302
|
+
installSpinner.text = `Installing ${skill.name}...`;
|
|
2303
|
+
const [primaryDir, ...symlinkDirs] = targetDirs;
|
|
2304
|
+
try {
|
|
2305
|
+
await installSkillFiles(skill.name, downloadData.files, primaryDir);
|
|
2306
|
+
} catch (dirErr) {
|
|
2307
|
+
const error = dirErr;
|
|
2308
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
2309
|
+
permissionError = true;
|
|
2310
|
+
failedDirs.add(primaryDir);
|
|
2311
|
+
}
|
|
2312
|
+
throw dirErr;
|
|
2313
|
+
}
|
|
2314
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
2315
|
+
for (const targetDir of symlinkDirs) {
|
|
2316
|
+
try {
|
|
2317
|
+
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
2318
|
+
} catch (dirErr) {
|
|
2319
|
+
const error = dirErr;
|
|
2320
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
2321
|
+
permissionError = true;
|
|
2322
|
+
failedDirs.add(targetDir);
|
|
2323
|
+
}
|
|
2324
|
+
throw dirErr;
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
installedSkills.push(`${skill.project}/${skill.name}`);
|
|
2328
|
+
} catch (err) {
|
|
2329
|
+
const error = err;
|
|
2330
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
2331
|
+
continue;
|
|
2332
|
+
}
|
|
2333
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2334
|
+
log.warn(`Failed to install ${skill.name}: ${errMsg}`);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
if (permissionError) {
|
|
2338
|
+
installSpinner.fail("Permission denied");
|
|
2339
|
+
log.blank();
|
|
2340
|
+
log.warn("Fix permissions with:");
|
|
2341
|
+
for (const dir of failedDirs) {
|
|
2342
|
+
const parentDir = join7(dir, "..");
|
|
2343
|
+
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
2344
|
+
}
|
|
2345
|
+
log.blank();
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
installSpinner.succeed(`Installed ${installedSkills.length} skill(s)`);
|
|
2349
|
+
trackEvent("detect_install", { skills: installedSkills, ides: targets.ides });
|
|
2350
|
+
await clearPendingPackages(cwd);
|
|
2351
|
+
const installedNames = selectedSkills.map((s) => s.name);
|
|
2352
|
+
logInstallSummary(targets, targetDirs, installedNames);
|
|
2353
|
+
}
|
|
2033
2354
|
async function suggestCommand(options) {
|
|
2034
2355
|
trackEvent("command", { name: "suggest" });
|
|
2035
2356
|
log.blank();
|
|
2357
|
+
const cwd = process.cwd();
|
|
2036
2358
|
const scanSpinner = ora3("Scanning project dependencies...").start();
|
|
2037
|
-
const deps = await detectProjectDependencies(
|
|
2359
|
+
const deps = await detectProjectDependencies(cwd);
|
|
2038
2360
|
if (deps.length === 0) {
|
|
2039
2361
|
scanSpinner.warn(pc7.yellow("No dependencies detected"));
|
|
2040
2362
|
log.info(`Try ${pc7.cyan("ctx7 skills search <keyword>")} to search manually`);
|
|
@@ -2147,7 +2469,7 @@ async function suggestCommand(options) {
|
|
|
2147
2469
|
}
|
|
2148
2470
|
throw dirErr;
|
|
2149
2471
|
}
|
|
2150
|
-
const primarySkillDir =
|
|
2472
|
+
const primarySkillDir = join7(primaryDir, skill.name);
|
|
2151
2473
|
for (const targetDir of symlinkDirs) {
|
|
2152
2474
|
try {
|
|
2153
2475
|
await symlinkSkill(skill.name, primarySkillDir, targetDir);
|
|
@@ -2175,7 +2497,7 @@ async function suggestCommand(options) {
|
|
|
2175
2497
|
log.blank();
|
|
2176
2498
|
log.warn("Fix permissions with:");
|
|
2177
2499
|
for (const dir of failedDirs) {
|
|
2178
|
-
const parentDir =
|
|
2500
|
+
const parentDir = join7(dir, "..");
|
|
2179
2501
|
log.dim(` sudo chown -R $(whoami) "${parentDir}"`);
|
|
2180
2502
|
}
|
|
2181
2503
|
log.blank();
|
|
@@ -2190,9 +2512,9 @@ async function suggestCommand(options) {
|
|
|
2190
2512
|
// src/constants.ts
|
|
2191
2513
|
import { readFileSync as readFileSync2 } from "fs";
|
|
2192
2514
|
import { fileURLToPath } from "url";
|
|
2193
|
-
import { dirname as dirname2, join as
|
|
2515
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
2194
2516
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
2195
|
-
var pkg = JSON.parse(readFileSync2(
|
|
2517
|
+
var pkg = JSON.parse(readFileSync2(join8(__dirname, "../package.json"), "utf-8"));
|
|
2196
2518
|
var VERSION = pkg.version;
|
|
2197
2519
|
var NAME = pkg.name;
|
|
2198
2520
|
|