skillscat 0.1.2 → 0.1.3

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.
@@ -1,6 +1,7 @@
1
1
  interface AddOptions {
2
2
  global?: boolean;
3
3
  agent?: string[];
4
+ repo?: boolean;
4
5
  skill?: string[];
5
6
  list?: boolean;
6
7
  yes?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAsBA,UAAU,UAAU;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAwBD,wBAAsB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2T5E"}
1
+ {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AA8BA,UAAU,UAAU;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAwBD,wBAAsB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA4S5E"}
@@ -0,0 +1,8 @@
1
+ interface ConvertOptions {
2
+ from?: string;
3
+ global?: boolean;
4
+ force?: boolean;
5
+ }
6
+ export declare function convert(targetAgentId: string, options: ConvertOptions): Promise<void>;
7
+ export {};
8
+ //# sourceMappingURL=convert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/commands/convert.ts"],"names":[],"mappings":"AAOA,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAaD,wBAAsB,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA2E3F"}
@@ -1 +1 @@
1
- {"version":3,"file":"info.d.ts","sourceRoot":"","sources":["../../src/commands/info.ts"],"names":[],"mappings":"AAMA,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ExD"}
1
+ {"version":3,"file":"info.d.ts","sourceRoot":"","sources":["../../src/commands/info.ts"],"names":[],"mappings":"AAMA,wBAAsB,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA+ExD"}
@@ -1 +1 @@
1
- {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"AAMA,UAAU,aAAa;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAsB,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DrF"}
1
+ {"version":3,"file":"remove.d.ts","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"AAMA,UAAU,aAAa;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,wBAAsB,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAiErF"}
@@ -0,0 +1,7 @@
1
+ interface ReportOptions {
2
+ reason?: 'security' | 'copyright';
3
+ details?: string;
4
+ }
5
+ export declare function report(slug: string, options?: ReportOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/commands/report.ts"],"names":[],"mappings":"AAKA,UAAU,aAAa;IACrB,MAAM,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAsBD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDrF"}
@@ -1 +1 @@
1
- {"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"AAKA,UAAU,aAAa;CAEtB;AA2KD,wBAAsB,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgLrF"}
1
+ {"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/commands/submit.ts"],"names":[],"mappings":"AAKA,UAAU,aAAa;CAEtB;AAyLD,wBAAsB,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAqMrF"}
@@ -0,0 +1,6 @@
1
+ interface ViewOptions {
2
+ output?: string;
3
+ }
4
+ export declare function view(slug: string, options?: ViewOptions): Promise<void>;
5
+ export {};
6
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/commands/view.ts"],"names":[],"mappings":"AAQA,UAAU,WAAW;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA0FD,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CjF"}
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import pc from 'picocolors';
4
- import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, unlinkSync, readdirSync, rmSync, realpathSync, statSync } from 'node:fs';
5
- import { join, dirname, posix, resolve, relative, isAbsolute } from 'node:path';
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, unlinkSync, readdirSync, rmSync, cpSync, realpathSync, statSync } from 'node:fs';
5
+ import { join, dirname, posix, basename, resolve, relative, isAbsolute } from 'node:path';
6
6
  import { createHash, randomBytes } from 'node:crypto';
7
7
  import os, { platform, homedir, hostname, release } from 'node:os';
8
8
  import * as readline from 'node:readline';
9
- import { spawnSync, execFileSync } from 'node:child_process';
9
+ import { spawnSync, execFileSync, execFile } from 'node:child_process';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import { createServer } from 'node:http';
12
12
 
@@ -98,6 +98,7 @@ const SKILL_DISCOVERY_PATHS = [
98
98
  'skills/.experimental',
99
99
  'skills/.system',
100
100
  '.opencode/skill',
101
+ '.agents',
101
102
  '.claude/skills',
102
103
  '.codex/skills',
103
104
  '.cursor/skills',
@@ -1103,7 +1104,6 @@ async function fetchGitHubSkillCompanionFiles(source, skillFilePath, snapshot) {
1103
1104
  const tree = await snapshot.getTree();
1104
1105
  const normalizedSkillFilePath = normalizeRepoPath(skillFilePath);
1105
1106
  const skillDir = getRepoDirPath(normalizedSkillFilePath);
1106
- const nestedSkillDirs = getNestedSkillDirectories(tree, normalizedSkillFilePath);
1107
1107
  const pathMap = await snapshot.getPathMap();
1108
1108
  const files = [];
1109
1109
  for (const item of tree) {
@@ -1114,8 +1114,6 @@ async function fetchGitHubSkillCompanionFiles(source, skillFilePath, snapshot) {
1114
1114
  continue;
1115
1115
  if (!isPathWithinDirectory(repoPath, skillDir))
1116
1116
  continue;
1117
- if (isPathInNestedSkillDirectory(repoPath, nestedSkillDirs))
1118
- continue;
1119
1117
  const relativePath = toRelativeSkillPath(repoPath, skillDir);
1120
1118
  if (!relativePath)
1121
1119
  continue;
@@ -1168,32 +1166,6 @@ async function resolveGitHubBlobOrSymlinkContent({ snapshot, item, currentPath,
1168
1166
  visited: nextVisited,
1169
1167
  });
1170
1168
  }
1171
- function getNestedSkillDirectories(tree, currentSkillFilePath) {
1172
- const currentSkillDir = getRepoDirPath(currentSkillFilePath);
1173
- const nested = new Set();
1174
- for (const item of tree) {
1175
- const itemPath = normalizeRepoPath(item.path);
1176
- if (item.type !== 'blob' || !itemPath.endsWith('/SKILL.md'))
1177
- continue;
1178
- if (itemPath === currentSkillFilePath)
1179
- continue;
1180
- const dir = getRepoDirPath(itemPath);
1181
- if (dir === currentSkillDir)
1182
- continue;
1183
- if (!isPathWithinDirectory(dir, currentSkillDir))
1184
- continue;
1185
- nested.add(dir);
1186
- }
1187
- return Array.from(nested);
1188
- }
1189
- function isPathInNestedSkillDirectory(path, nestedSkillDirs) {
1190
- for (const nestedDir of nestedSkillDirs) {
1191
- if (isPathWithinDirectory(path, nestedDir)) {
1192
- return true;
1193
- }
1194
- }
1195
- return false;
1196
- }
1197
1169
  function normalizeRepoPath(path) {
1198
1170
  const normalized = path.replace(/^\/+|\/+$/g, '');
1199
1171
  if (!normalized)
@@ -1658,6 +1630,27 @@ function parseSlug(slug) {
1658
1630
  }
1659
1631
  return { owner: match[1], name: match[2] };
1660
1632
  }
1633
+ /**
1634
+ * Encode a slug for use in /skills/[owner]/[...name] paths
1635
+ * @param slug - Skill slug in format "owner/name[/nested]"
1636
+ * @returns Encoded path segment like "owner/name" (without leading slash)
1637
+ */
1638
+ function encodeSlugForSkillPath(slug) {
1639
+ const { owner, name } = parseSlug(slug);
1640
+ const encodedName = name
1641
+ .split('/')
1642
+ .map((segment) => encodeURIComponent(segment))
1643
+ .join('/');
1644
+ return `${encodeURIComponent(owner)}/${encodedName}`;
1645
+ }
1646
+ /**
1647
+ * Build the canonical web skill path from a slug
1648
+ * @param slug - Skill slug in format "owner/name[/nested]"
1649
+ * @returns Path like "/skills/owner/name"
1650
+ */
1651
+ function buildSkillPath(slug) {
1652
+ return `/skills/${encodeSlugForSkillPath(slug)}`;
1653
+ }
1661
1654
 
1662
1655
  const GITHUB_API = 'https://api.github.com';
1663
1656
  async function getAuthHeaders() {
@@ -1887,6 +1880,7 @@ function submitRepoForIndexingInBackground(source) {
1887
1880
  }
1888
1881
  }
1889
1882
 
1883
+ const FALLBACK_AGENT_ID = 'agents';
1890
1884
  function sanitizeSkillDirName(skillName) {
1891
1885
  const sanitized = skillName
1892
1886
  .replace(/[\\/]/g, '-')
@@ -1902,6 +1896,13 @@ function sanitizeSkillDirName(skillName) {
1902
1896
  return sanitized;
1903
1897
  }
1904
1898
  const AGENTS = [
1899
+ {
1900
+ id: FALLBACK_AGENT_ID,
1901
+ name: '.agents',
1902
+ projectPath: '.agents/',
1903
+ globalPath: join(homedir(), '.agents'),
1904
+ aliases: ['.agents', 'generic']
1905
+ },
1905
1906
  {
1906
1907
  id: 'amp',
1907
1908
  name: 'Amp',
@@ -2023,21 +2024,51 @@ const AGENTS = [
2023
2024
  globalPath: join(homedir(), '.codeium', 'windsurf', 'skills')
2024
2025
  }
2025
2026
  ];
2027
+ function normalizeAgentId(id) {
2028
+ return id.trim().toLowerCase().replace(/\s+/g, '-');
2029
+ }
2030
+ function getGlobalDetectionPath(agent) {
2031
+ const normalized = agent.globalPath.replace(/[\\/]+$/, '');
2032
+ const container = normalized.replace(/[\\/](?:skill|skills)$/i, '');
2033
+ return container || normalized;
2034
+ }
2035
+ function getAgentBasePath(agent, global, cwd = process.cwd()) {
2036
+ return global ? agent.globalPath : join(cwd, agent.projectPath);
2037
+ }
2026
2038
  /**
2027
2039
  * Detect which agents are installed by checking for their config directories
2028
2040
  */
2029
2041
  function detectInstalledAgents() {
2030
2042
  return AGENTS.filter(agent => {
2031
- // Check if global path exists (indicating agent is installed)
2032
- const globalDir = agent.globalPath.replace(/\/skills\/?$/, '').replace(/\/skill\/?$/, '');
2033
- return existsSync(globalDir);
2043
+ const globalDir = getGlobalDetectionPath(agent);
2044
+ return existsSync(agent.globalPath) || existsSync(globalDir);
2034
2045
  });
2035
2046
  }
2047
+ function detectProjectAgents(cwd = process.cwd()) {
2048
+ return AGENTS.filter((agent) => existsSync(getAgentBasePath(agent, false, cwd)));
2049
+ }
2050
+ function preferSpecificAgents(agents) {
2051
+ const specificAgents = agents.filter((agent) => agent.id !== FALLBACK_AGENT_ID);
2052
+ return specificAgents.length > 0 ? specificAgents : agents;
2053
+ }
2054
+ function detectPreferredAgents(global, cwd = process.cwd()) {
2055
+ if (global) {
2056
+ const installedAgents = preferSpecificAgents(detectInstalledAgents());
2057
+ return installedAgents.length > 0 ? installedAgents : [getFallbackAgent()];
2058
+ }
2059
+ const projectAgents = preferSpecificAgents(detectProjectAgents(cwd));
2060
+ if (projectAgents.length > 0) {
2061
+ return projectAgents;
2062
+ }
2063
+ const installedAgents = preferSpecificAgents(detectInstalledAgents());
2064
+ return installedAgents.length > 0 ? installedAgents : [getFallbackAgent()];
2065
+ }
2036
2066
  /**
2037
2067
  * Get agent by ID
2038
2068
  */
2039
2069
  function getAgentById(id) {
2040
- return AGENTS.find(a => a.id === id || a.id === id.toLowerCase().replace(/\s+/g, '-'));
2070
+ const normalized = normalizeAgentId(id);
2071
+ return AGENTS.find((agent) => agent.id === normalized || agent.aliases?.some((alias) => normalizeAgentId(alias) === normalized));
2041
2072
  }
2042
2073
  /**
2043
2074
  * Get agents by IDs
@@ -2045,11 +2076,18 @@ function getAgentById(id) {
2045
2076
  function getAgentsByIds(ids) {
2046
2077
  return ids.map(id => getAgentById(id)).filter((a) => a !== undefined);
2047
2078
  }
2079
+ function getFallbackAgent() {
2080
+ const fallbackAgent = getAgentById(FALLBACK_AGENT_ID);
2081
+ if (!fallbackAgent) {
2082
+ throw new Error('Fallback .agents target is not configured');
2083
+ }
2084
+ return fallbackAgent;
2085
+ }
2048
2086
  /**
2049
2087
  * Get skill installation path for an agent
2050
2088
  */
2051
- function getSkillPath(agent, skillName, global) {
2052
- const basePath = global ? agent.globalPath : join(process.cwd(), agent.projectPath);
2089
+ function getSkillPath(agent, skillName, global, cwd = process.cwd()) {
2090
+ const basePath = getAgentBasePath(agent, global, cwd);
2053
2091
  return join(basePath, sanitizeSkillDirName(skillName));
2054
2092
  }
2055
2093
 
@@ -2111,6 +2149,7 @@ function normalizeSkill(raw) {
2111
2149
  sha: typeof candidate.sha === 'string' ? candidate.sha : undefined,
2112
2150
  path,
2113
2151
  contentHash: typeof candidate.contentHash === 'string' ? candidate.contentHash : undefined,
2152
+ installRoot: typeof candidate.installRoot === 'string' && candidate.installRoot ? candidate.installRoot : undefined,
2114
2153
  };
2115
2154
  }
2116
2155
  function loadDb() {
@@ -2155,11 +2194,75 @@ function sameSource(a, b) {
2155
2194
  function sameInstallationIdentity(a, b) {
2156
2195
  return (a.name === b.name &&
2157
2196
  a.global === b.global &&
2197
+ (a.installRoot ?? '') === (b.installRoot ?? '') &&
2158
2198
  a.path === b.path &&
2159
2199
  (a.registrySlug ?? '') === (b.registrySlug ?? '') &&
2160
2200
  getUpdateStrategy$1(a) === getUpdateStrategy$1(b) &&
2161
2201
  sameSource(a.source, b.source));
2162
2202
  }
2203
+ function sameLegacyProjectIdentity(existing, next) {
2204
+ return (!existing.global &&
2205
+ !next.global &&
2206
+ !existing.installRoot &&
2207
+ Boolean(next.installRoot) &&
2208
+ existing.name === next.name &&
2209
+ existing.path === next.path &&
2210
+ (existing.registrySlug ?? '') === (next.registrySlug ?? '') &&
2211
+ getUpdateStrategy$1(existing) === getUpdateStrategy$1(next) &&
2212
+ sameSource(existing.source, next.source));
2213
+ }
2214
+ function saveDbIfChanged(db, changed) {
2215
+ if (changed) {
2216
+ saveDb(db);
2217
+ }
2218
+ return db;
2219
+ }
2220
+ function reconcileInstallations(db) {
2221
+ let changed = false;
2222
+ const cwd = process.cwd();
2223
+ db.skills = db.skills.flatMap((skill) => {
2224
+ let reconciledSkill = skill;
2225
+ if (!reconciledSkill.global && !reconciledSkill.installRoot) {
2226
+ const hasCurrentWorkspaceMatch = reconciledSkill.agents.some((agentId) => {
2227
+ const agent = getAgentById(agentId);
2228
+ if (!agent) {
2229
+ return false;
2230
+ }
2231
+ const skillDir = getSkillPath(agent, reconciledSkill.name, false, cwd);
2232
+ return existsSync(join(skillDir, 'SKILL.md'));
2233
+ });
2234
+ if (!hasCurrentWorkspaceMatch) {
2235
+ return [reconciledSkill];
2236
+ }
2237
+ reconciledSkill = {
2238
+ ...reconciledSkill,
2239
+ installRoot: cwd,
2240
+ };
2241
+ changed = true;
2242
+ }
2243
+ const remainingAgents = reconciledSkill.agents.filter((agentId) => {
2244
+ const agent = getAgentById(agentId);
2245
+ if (!agent) {
2246
+ changed = true;
2247
+ return false;
2248
+ }
2249
+ const skillDir = getSkillPath(agent, reconciledSkill.name, reconciledSkill.global, reconciledSkill.installRoot);
2250
+ return existsSync(join(skillDir, 'SKILL.md'));
2251
+ });
2252
+ if (remainingAgents.length === reconciledSkill.agents.length) {
2253
+ return [reconciledSkill];
2254
+ }
2255
+ changed = true;
2256
+ if (remainingAgents.length === 0) {
2257
+ return [];
2258
+ }
2259
+ return [{
2260
+ ...reconciledSkill,
2261
+ agents: remainingAgents,
2262
+ }];
2263
+ });
2264
+ return saveDbIfChanged(db, changed);
2265
+ }
2163
2266
  /**
2164
2267
  * Record a skill installation
2165
2268
  */
@@ -2170,7 +2273,8 @@ function recordInstallation(skill) {
2170
2273
  return;
2171
2274
  }
2172
2275
  // Replace only exact same installation identity.
2173
- db.skills = db.skills.filter((existing) => !sameInstallationIdentity(existing, normalized));
2276
+ db.skills = db.skills.filter((existing) => !sameInstallationIdentity(existing, normalized) &&
2277
+ !sameLegacyProjectIdentity(existing, normalized));
2174
2278
  db.skills.push(normalized);
2175
2279
  saveDb(db);
2176
2280
  }
@@ -2192,6 +2296,9 @@ function removeInstallation(skillName, options) {
2192
2296
  if (options?.global !== undefined && skill.global !== options.global) {
2193
2297
  return [skill];
2194
2298
  }
2299
+ if (options?.installRoot !== undefined && (skill.installRoot ?? '') !== options.installRoot) {
2300
+ return [skill];
2301
+ }
2195
2302
  if (targetAgents && targetAgents.length > 0) {
2196
2303
  const remainingAgents = skill.agents.filter((agentId) => !targetAgents.includes(agentId));
2197
2304
  if (remainingAgents.length === 0) {
@@ -2203,11 +2310,60 @@ function removeInstallation(skillName, options) {
2203
2310
  });
2204
2311
  saveDb(db);
2205
2312
  }
2313
+ function copyInstallationAgent(sourceAgentId, targetAgentId, options) {
2314
+ if (sourceAgentId === targetAgentId) {
2315
+ return 0;
2316
+ }
2317
+ const db = loadDb();
2318
+ let updated = 0;
2319
+ const sourceSkillDirs = options?.sourceSkillDirs ? new Set(options.sourceSkillDirs) : null;
2320
+ const sourceAgent = getAgentById(sourceAgentId);
2321
+ db.skills = db.skills.map((skill) => {
2322
+ if (options?.global !== undefined && skill.global !== options.global) {
2323
+ return skill;
2324
+ }
2325
+ if (!skill.agents.includes(sourceAgentId) || skill.agents.includes(targetAgentId)) {
2326
+ return skill;
2327
+ }
2328
+ let installRoot = skill.installRoot;
2329
+ if (options?.installRoot !== undefined) {
2330
+ if (installRoot) {
2331
+ if (installRoot !== options.installRoot) {
2332
+ return skill;
2333
+ }
2334
+ }
2335
+ else if (!skill.global &&
2336
+ sourceAgent &&
2337
+ existsSync(join(getSkillPath(sourceAgent, skill.name, false, options.installRoot), 'SKILL.md'))) {
2338
+ installRoot = options.installRoot;
2339
+ }
2340
+ else {
2341
+ return skill;
2342
+ }
2343
+ }
2344
+ if (sourceSkillDirs && sourceAgent) {
2345
+ const sourceSkillDir = getSkillPath(sourceAgent, skill.name, skill.global, installRoot);
2346
+ if (!sourceSkillDirs.has(sourceSkillDir)) {
2347
+ return skill;
2348
+ }
2349
+ }
2350
+ updated += 1;
2351
+ return {
2352
+ ...skill,
2353
+ installRoot,
2354
+ agents: [...skill.agents, targetAgentId],
2355
+ };
2356
+ });
2357
+ if (updated > 0) {
2358
+ saveDb(db);
2359
+ }
2360
+ return updated;
2361
+ }
2206
2362
  /**
2207
2363
  * Get all installed skills
2208
2364
  */
2209
2365
  function getInstalledSkills() {
2210
- const db = loadDb();
2366
+ const db = reconcileInstallations(loadDb());
2211
2367
  return db.skills;
2212
2368
  }
2213
2369
 
@@ -2301,6 +2457,7 @@ function box(content, title) {
2301
2457
  const COMPANION_MANIFEST_FILE = '.skillscat-companion-files.json';
2302
2458
  const COMPANION_MANIFEST_VERSION = 1;
2303
2459
  async function add(source, options) {
2460
+ const explicitRepoInstall = options.repo === true;
2304
2461
  const repoSource = parseSource(source);
2305
2462
  if (!repoSource) {
2306
2463
  error('Invalid source. Supported formats:');
@@ -2328,6 +2485,7 @@ async function add(source, options) {
2328
2485
  sourceInput: source,
2329
2486
  repoSource,
2330
2487
  requestedSkillNames: options.skill ?? [],
2488
+ explicitRepoInstall,
2331
2489
  explicitRefBypassRegistry: isExplicitGitHubRefSource,
2332
2490
  githubSnapshot,
2333
2491
  });
@@ -2369,12 +2527,17 @@ async function add(source, options) {
2369
2527
  }
2370
2528
  console.log(pc.dim('─'.repeat(50)));
2371
2529
  console.log(pc.dim('Install with:'));
2372
- console.log(` ${pc.cyan(`npx skillscat add ${source}`)}`);
2530
+ console.log(` ${pc.cyan(formatAddCommand(source, options))}`);
2373
2531
  return;
2374
2532
  }
2375
2533
  if (selectionMode === 'install-all' && !options.yes && (!options.skill || options.skill.length === 0)) {
2376
2534
  console.log();
2377
- info$1(`No published skill slug matched ${pc.cyan(sourceLabel)}.`);
2535
+ if (explicitRepoInstall) {
2536
+ info$1(`Treating ${pc.cyan(sourceLabel)} as a repository install.`);
2537
+ }
2538
+ else {
2539
+ info$1(`No published skill slug matched ${pc.cyan(sourceLabel)}.`);
2540
+ }
2378
2541
  console.log(pc.dim(`Found ${resolvedSkills.length} published skill(s) in repository ${sourceLabel}:`));
2379
2542
  for (const entry of resolvedSkills) {
2380
2543
  console.log(pc.dim(` - ${entry.skill.name}${formatSkillPathHint(entry.skill.path)}`));
@@ -2404,6 +2567,8 @@ async function add(source, options) {
2404
2567
  }
2405
2568
  // Detect or select agents
2406
2569
  let targetAgents;
2570
+ const isGlobal = options.global ?? false;
2571
+ let usedFallbackAgent = false;
2407
2572
  if (options.agent && options.agent.length > 0) {
2408
2573
  targetAgents = getAgentsByIds(options.agent);
2409
2574
  if (targetAgents.length === 0) {
@@ -2416,38 +2581,12 @@ async function add(source, options) {
2416
2581
  }
2417
2582
  }
2418
2583
  else {
2419
- targetAgents = detectInstalledAgents();
2420
- if (targetAgents.length === 0) {
2421
- if (!options.yes) {
2422
- console.log();
2423
- warn('No coding agents detected.');
2424
- console.log(pc.dim('Select agents to install skills for:'));
2425
- console.log();
2426
- for (let i = 0; i < AGENTS.length; i++) {
2427
- console.log(` ${pc.dim(`${i + 1}.`)} ${AGENTS[i].name} (${AGENTS[i].id})`);
2428
- }
2429
- console.log();
2430
- const response = await prompt('Enter agent numbers (comma-separated) or "all": ');
2431
- if (response.toLowerCase() === 'all') {
2432
- targetAgents = AGENTS;
2433
- }
2434
- else {
2435
- const indices = response.split(',').map((s) => parseInt(s.trim()) - 1);
2436
- targetAgents = indices
2437
- .filter((i) => i >= 0 && i < AGENTS.length)
2438
- .map((i) => AGENTS[i]);
2439
- }
2440
- if (targetAgents.length === 0) {
2441
- error('No agents selected.');
2442
- process.exit(1);
2443
- }
2444
- }
2445
- else {
2446
- targetAgents = AGENTS.filter((a) => a.id === 'claude-code');
2447
- }
2584
+ targetAgents = detectPreferredAgents(isGlobal);
2585
+ usedFallbackAgent = targetAgents.length === 1 && targetAgents[0]?.id === FALLBACK_AGENT_ID;
2586
+ if (usedFallbackAgent) {
2587
+ warn('No agent-specific directory detected. Installing to .agents.');
2448
2588
  }
2449
2589
  }
2450
- const isGlobal = options.global ?? false;
2451
2590
  const locationLabel = isGlobal ? 'global' : 'project';
2452
2591
  console.log();
2453
2592
  console.log(pc.bold(`Installing ${selectedEntries.length} skill(s) to ${targetAgents.length} agent(s):`));
@@ -2460,7 +2599,7 @@ async function add(source, options) {
2460
2599
  console.log();
2461
2600
  console.log(pc.dim('Target agents:'));
2462
2601
  for (const agent of targetAgents) {
2463
- const path = isGlobal ? agent.globalPath : join(process.cwd(), agent.projectPath);
2602
+ const path = getAgentBasePath(agent, isGlobal);
2464
2603
  console.log(` ${pc.cyan('•')} ${agent.name} → ${pc.dim(path)}`);
2465
2604
  }
2466
2605
  console.log();
@@ -2546,6 +2685,7 @@ async function add(source, options) {
2546
2685
  sha: skill.sha,
2547
2686
  path: skill.path,
2548
2687
  contentHash: skill.contentHash,
2688
+ installRoot: isGlobal ? undefined : process.cwd(),
2549
2689
  });
2550
2690
  trackInstallation(entry.trackingSlug).catch(() => { });
2551
2691
  }
@@ -2567,12 +2707,16 @@ async function add(source, options) {
2567
2707
  console.log();
2568
2708
  console.log(pc.dim('Skills are now available in your coding agents.'));
2569
2709
  console.log(pc.dim('Restart your agent or start a new session to use them.'));
2710
+ if (usedFallbackAgent) {
2711
+ console.log(pc.dim('Need a tool-specific copy later? Run `npx skillscat convert <agent>` to copy from .agents.'));
2712
+ }
2570
2713
  }
2571
- async function resolveInstallSkills({ sourceInput, repoSource, requestedSkillNames, explicitRefBypassRegistry, githubSnapshot, }) {
2714
+ async function resolveInstallSkills({ sourceInput, repoSource, requestedSkillNames, explicitRepoInstall, explicitRefBypassRegistry, githubSnapshot, }) {
2572
2715
  const requestedNamesLower = new Set(requestedSkillNames.map((name) => name.toLowerCase()));
2573
2716
  const needsRegistryFirst = repoSource.platform === 'github' && !explicitRefBypassRegistry;
2574
- const preferSlugLookup = isAmbiguousSlugOrRepoInput(sourceInput, repoSource);
2575
- const canShortCircuitExactSlug = preferSlugLookup && requestedNamesLower.size === 0;
2717
+ const ambiguousSlugOrRepoInput = isAmbiguousSlugOrRepoInput(sourceInput, repoSource);
2718
+ const preferRepoSelection = explicitRepoInstall || ambiguousSlugOrRepoInput;
2719
+ const canShortCircuitExactSlug = ambiguousSlugOrRepoInput && !explicitRepoInstall && requestedNamesLower.size === 0;
2576
2720
  let resolved = [];
2577
2721
  let selectionMode = 'default';
2578
2722
  let registryRepoMatchesFound = false;
@@ -2605,7 +2749,7 @@ async function resolveInstallSkills({ sourceInput, repoSource, requestedSkillNam
2605
2749
  if (requestedNamesLower.size > 0) {
2606
2750
  selectedSummaries = summaries.filter((item) => requestedNamesLower.has(item.name.toLowerCase()));
2607
2751
  }
2608
- else if (preferSlugLookup) {
2752
+ else if (preferRepoSelection) {
2609
2753
  selectionMode = 'install-all';
2610
2754
  }
2611
2755
  if (selectedSummaries.length > 0) {
@@ -2649,7 +2793,7 @@ async function resolveInstallSkills({ sourceInput, repoSource, requestedSkillNam
2649
2793
  throw err;
2650
2794
  }
2651
2795
  // Fallback: preserve existing behavior for registry slugs or private skills.
2652
- if (resolved.length === 0 && true) {
2796
+ if (!explicitRepoInstall && resolved.length === 0 && true) {
2653
2797
  const registrySkill = await fetchSkill(sourceInput).catch(() => null);
2654
2798
  if (registrySkill?.content) {
2655
2799
  resolved.push(toRegistryResolvedSkill(registrySkill, sourceInput, repoSource));
@@ -2971,6 +3115,9 @@ function formatSkillPathHint(path) {
2971
3115
  const normalized = normalizeSkillPath(path);
2972
3116
  return normalized ? ` (${normalized})` : '';
2973
3117
  }
3118
+ function formatAddCommand(source, options) {
3119
+ return options.repo ? `npx skillscat add ${source} --repo` : `npx skillscat add ${source}`;
3120
+ }
2974
3121
  function toSkillFilePath(path) {
2975
3122
  const normalized = normalizeSkillPath(path);
2976
3123
  return normalized ? `${normalized}/SKILL.md` : 'SKILL.md';
@@ -3095,7 +3242,7 @@ async function list(options) {
3095
3242
  warn('No skills installed.');
3096
3243
  console.log();
3097
3244
  console.log(pc.dim('Install skills with:'));
3098
- console.log(` ${pc.cyan('npx skillscat add <owner>/<repo>')}`);
3245
+ console.log(` ${pc.cyan('npx skillscat add <slug>')}`);
3099
3246
  console.log();
3100
3247
  console.log(pc.dim('Or search for skills:'));
3101
3248
  console.log(` ${pc.cyan('npx skillscat search <query>')}`);
@@ -3229,13 +3376,15 @@ async function search(query, options = {}) {
3229
3376
  console.log(pc.dim('─'.repeat(50)));
3230
3377
  console.log();
3231
3378
  console.log(pc.dim('Install a skill:'));
3232
- console.log(` ${pc.cyan('npx skillscat add <owner>/<repo>')}`);
3379
+ console.log(` ${pc.cyan('npx skillscat add <slug>')}`);
3233
3380
  console.log();
3234
- console.log(pc.dim('View skill details:'));
3381
+ console.log(pc.dim('Inspect a repository first:'));
3235
3382
  console.log(` ${pc.cyan('npx skillscat info <owner>/<repo>')}`);
3236
3383
  }
3237
3384
 
3238
3385
  async function remove(skillName, options) {
3386
+ // Reconcile tracked installs so manual deletions do not leave stale records behind.
3387
+ getInstalledSkills();
3239
3388
  // Determine which agents to check
3240
3389
  let agents;
3241
3390
  if (options.agent && options.agent.length > 0) {
@@ -3270,6 +3419,7 @@ async function remove(skillName, options) {
3270
3419
  removeInstallation(skillName, {
3271
3420
  agents: agents.map((agent) => agent.id),
3272
3421
  global: isGlobal,
3422
+ installRoot: isGlobal ? undefined : process.cwd(),
3273
3423
  });
3274
3424
  }
3275
3425
  if (removed === 0) {
@@ -3443,7 +3593,7 @@ async function update(skillName, options) {
3443
3593
  skillAgents.push(...agents.filter(a => skill.agents.includes(a.id)));
3444
3594
  }
3445
3595
  for (const agent of skillAgents) {
3446
- const skillDir = getSkillPath(agent, skill.name, skill.global);
3596
+ const skillDir = getSkillPath(agent, skill.name, skill.global, skill.installRoot);
3447
3597
  const skillFile = join(skillDir, 'SKILL.md');
3448
3598
  try {
3449
3599
  mkdirSync(dirname(skillFile), { recursive: true });
@@ -3471,6 +3621,78 @@ async function update(skillName, options) {
3471
3621
  success(`Updated ${updated} skill(s) successfully!`);
3472
3622
  }
3473
3623
 
3624
+ function getSkillDirectories(basePath) {
3625
+ if (!existsSync(basePath)) {
3626
+ return [];
3627
+ }
3628
+ return readdirSync(basePath, { withFileTypes: true })
3629
+ .filter((entry) => entry.isDirectory())
3630
+ .map((entry) => join(basePath, entry.name))
3631
+ .filter((skillDir) => existsSync(join(skillDir, 'SKILL.md')));
3632
+ }
3633
+ async function convert(targetAgentId, options) {
3634
+ const isGlobal = options.global ?? false;
3635
+ const sourceAgent = getAgentById(options.from ?? FALLBACK_AGENT_ID);
3636
+ const targetAgent = getAgentById(targetAgentId);
3637
+ if (!sourceAgent) {
3638
+ error(`Invalid source agent: ${options.from ?? FALLBACK_AGENT_ID}`);
3639
+ process.exit(1);
3640
+ }
3641
+ if (!targetAgent) {
3642
+ error(`Invalid target agent: ${targetAgentId}`);
3643
+ process.exit(1);
3644
+ }
3645
+ const sourceBase = getAgentBasePath(sourceAgent, isGlobal);
3646
+ const targetBase = getAgentBasePath(targetAgent, isGlobal);
3647
+ if (sourceBase === targetBase) {
3648
+ error('Source and target directories are the same.');
3649
+ process.exit(1);
3650
+ }
3651
+ const sourceSkills = getSkillDirectories(sourceBase);
3652
+ if (sourceSkills.length === 0) {
3653
+ warn(`No skills found in ${sourceAgent.name}.`);
3654
+ console.log(pc.dim(`Checked: ${sourceBase}`));
3655
+ return;
3656
+ }
3657
+ let copied = 0;
3658
+ let skipped = 0;
3659
+ const copiedSkillDirs = [];
3660
+ for (const sourceSkillDir of sourceSkills) {
3661
+ const skillName = basename(sourceSkillDir);
3662
+ const targetSkillDir = join(targetBase, skillName);
3663
+ if (existsSync(targetSkillDir)) {
3664
+ if (!options.force) {
3665
+ skipped += 1;
3666
+ continue;
3667
+ }
3668
+ rmSync(targetSkillDir, { recursive: true, force: true });
3669
+ }
3670
+ mkdirSync(dirname(targetSkillDir), { recursive: true });
3671
+ cpSync(sourceSkillDir, targetSkillDir, { recursive: true });
3672
+ copied += 1;
3673
+ copiedSkillDirs.push(sourceSkillDir);
3674
+ }
3675
+ if (copied === 0) {
3676
+ warn(`All ${sourceSkills.length} skill(s) already exist in ${targetAgent.name}.`);
3677
+ console.log(pc.dim('Use `--force` to overwrite the target copy.'));
3678
+ return;
3679
+ }
3680
+ const trackedUpdates = copyInstallationAgent(sourceAgent.id, targetAgent.id, {
3681
+ global: isGlobal,
3682
+ installRoot: isGlobal ? undefined : process.cwd(),
3683
+ sourceSkillDirs: copiedSkillDirs,
3684
+ });
3685
+ console.log();
3686
+ success(`Copied ${copied} skill(s) from ${sourceAgent.name} to ${targetAgent.name}.`);
3687
+ console.log(pc.dim(`${sourceBase} → ${targetBase}`));
3688
+ if (skipped > 0) {
3689
+ info$1(`Skipped ${skipped} existing skill(s). Use --force to overwrite them.`);
3690
+ }
3691
+ if (trackedUpdates > 0) {
3692
+ console.log(pc.dim(`Updated ${trackedUpdates} tracked installation(s) so future updates also target ${targetAgent.name}.`));
3693
+ }
3694
+ }
3695
+
3474
3696
  const PACKAGE_NAME = 'skillscat';
3475
3697
  function safeExec(command, args) {
3476
3698
  try {
@@ -3701,8 +3923,9 @@ async function info(source) {
3701
3923
  }
3702
3924
  console.log(pc.dim('─'.repeat(50)));
3703
3925
  console.log();
3704
- console.log(pc.bold('Install:'));
3705
- console.log(` ${pc.cyan(`npx skillscat add ${source}`)}`);
3926
+ console.log(pc.bold('Install published skills by slug:'));
3927
+ console.log(` ${pc.cyan('npx skillscat add <slug>')}`);
3928
+ console.log(` ${pc.dim('Use search or the web detail page to discover the exact published slug for this repository.')}`);
3706
3929
  console.log();
3707
3930
  // Show compatible agents
3708
3931
  console.log(pc.bold('Compatible agents:'));
@@ -4153,6 +4376,14 @@ async function publish(skillPath, options) {
4153
4376
  }
4154
4377
  }
4155
4378
 
4379
+ function printSubmitMessage(message) {
4380
+ for (const line of message.split('\n')) {
4381
+ console.log(pc.dim(line));
4382
+ }
4383
+ }
4384
+ const IGNORED_DISCOVERY_DIRS = new Set([
4385
+ '.git',
4386
+ ]);
4156
4387
  /**
4157
4388
  * Check if a URL is a valid GitHub URL
4158
4389
  */
@@ -4243,8 +4474,7 @@ function findSkillMd(cwd, maxDepth = 3) {
4243
4474
  try {
4244
4475
  const entries = readdirSync(dir);
4245
4476
  for (const entry of entries) {
4246
- // Skip dot folders
4247
- if (entry.startsWith('.'))
4477
+ if (IGNORED_DISCOVERY_DIRS.has(entry))
4248
4478
  continue;
4249
4479
  const fullPath = join(dir, entry);
4250
4480
  try {
@@ -4425,26 +4655,46 @@ async function submit(urlArg, _options) {
4425
4655
  process.exit(1);
4426
4656
  }
4427
4657
  if (response.status === 400) {
4428
- console.error(pc.red(`Submission failed: ${result.error || 'Invalid request'}`));
4429
- if (result.error?.includes('SKILL.md')) {
4658
+ const submitError = result.error || result.message || 'Invalid request';
4659
+ console.error(pc.red(`Submission failed: ${submitError}`));
4660
+ if (result.code === 'no_skill_md_found') {
4430
4661
  console.log();
4431
- console.log(pc.dim('Make sure your repository has a SKILL.md file in the root directory.'));
4662
+ console.log(pc.dim('Make sure your repository has a SKILL.md file in the repository root or a supported subdirectory, including dot folders.'));
4432
4663
  console.log(pc.dim('Learn more: https://skillscat.com/docs/skill-format'));
4433
4664
  }
4434
- if (result.error?.includes('fork') || result.error?.includes('Fork')) {
4665
+ if (result.code === 'fork_no_unique_commits') {
4666
+ console.log();
4667
+ console.log(pc.dim('Please submit the original repository, or add your own commits to the fork first.'));
4668
+ }
4669
+ if (result.code === 'fork_behind_upstream') {
4435
4670
  console.log();
4436
- console.log(pc.dim('Please submit the original repository instead of a fork.'));
4671
+ console.log(pc.dim('Please sync your fork with upstream before submitting it.'));
4437
4672
  }
4438
4673
  process.exit(1);
4439
4674
  }
4440
4675
  if (!response.ok || !result.success) {
4441
4676
  console.error(pc.red(`Submission failed: ${result.error || result.message || 'Unknown error'}`));
4677
+ if (result.code === 'github_rate_limited' && result.retryAfterSeconds) {
4678
+ console.log(pc.dim(`Try again in about ${result.retryAfterSeconds} seconds.`));
4679
+ }
4442
4680
  process.exit(1);
4443
4681
  }
4444
4682
  // Success!
4683
+ const submittedCount = result.submitted ?? 0;
4684
+ const existingCount = result.existing ?? 0;
4445
4685
  console.log();
4446
- console.log(pc.green('Skill submitted successfully!'));
4447
- console.log(pc.dim(result.message || 'It will appear in the catalog once processed.'));
4686
+ if (existingCount > 0 && submittedCount === 0) {
4687
+ console.log(pc.green('No new submission needed.'));
4688
+ }
4689
+ else {
4690
+ console.log(pc.green('Skill submitted successfully!'));
4691
+ }
4692
+ printSubmitMessage(result.message || 'It will appear in the catalog once processed.');
4693
+ if (result.existingSlug) {
4694
+ console.log();
4695
+ console.log(`View it at: ${pc.cyan(`${baseUrl}/skills/${result.existingSlug}`)}`);
4696
+ console.log(`Install with: ${pc.cyan(`skillscat add ${result.existingSlug}`)}`);
4697
+ }
4448
4698
  }
4449
4699
  catch (error) {
4450
4700
  console.error(pc.red('Failed to connect to SkillsCat.'));
@@ -4455,6 +4705,60 @@ async function submit(urlArg, _options) {
4455
4705
  }
4456
4706
  }
4457
4707
 
4708
+ function normalizeReason(value) {
4709
+ if (!value)
4710
+ return null;
4711
+ const normalized = value.trim().toLowerCase();
4712
+ if (normalized === 'security' || normalized === 'copyright') {
4713
+ return normalized;
4714
+ }
4715
+ return null;
4716
+ }
4717
+ async function report(slug, options = {}) {
4718
+ const token = await getValidToken();
4719
+ if (!token) {
4720
+ error('Authentication required.');
4721
+ console.log(pc.dim('Run `skillscat login` first.'));
4722
+ process.exit(1);
4723
+ }
4724
+ let reason = normalizeReason(options.reason);
4725
+ if (!reason) {
4726
+ const answer = await prompt('Report reason [security/copyright]: ');
4727
+ reason = normalizeReason(answer);
4728
+ }
4729
+ if (!reason) {
4730
+ error('Invalid report reason. Use `security` or `copyright`.');
4731
+ process.exit(1);
4732
+ }
4733
+ const baseUrl = getBaseUrl();
4734
+ const response = await fetch(`${baseUrl}/api/skills/${encodeURIComponent(slug)}/report`, {
4735
+ method: 'POST',
4736
+ headers: {
4737
+ Authorization: `Bearer ${token}`,
4738
+ 'Content-Type': 'application/json',
4739
+ Origin: baseUrl,
4740
+ 'User-Agent': 'skillscat-cli/0.1.0',
4741
+ },
4742
+ body: JSON.stringify({
4743
+ reason,
4744
+ details: options.details || undefined,
4745
+ }),
4746
+ });
4747
+ const payload = await response.json();
4748
+ if (!response.ok || !payload.success) {
4749
+ error(payload.error || payload.message || 'Failed to submit report');
4750
+ process.exit(1);
4751
+ }
4752
+ success(payload.message || 'Report submitted');
4753
+ if (reason === 'security' && payload.report) {
4754
+ console.log(` Open security reports: ${pc.cyan(String(payload.report.openSecurityReportCount))}`);
4755
+ console.log(` Risk level: ${pc.cyan(payload.report.riskLevel)}`);
4756
+ if (payload.report.premiumEscalated) {
4757
+ console.log(` ${pc.yellow('Premium review queued')}`);
4758
+ }
4759
+ }
4760
+ }
4761
+
4458
4762
  /**
4459
4763
  * Find skill by slug using two-segment path
4460
4764
  */
@@ -4548,6 +4852,208 @@ async function unpublishSkill(slug, options) {
4548
4852
  }
4549
4853
  }
4550
4854
 
4855
+ function hasCommand(command) {
4856
+ const trimmed = command.trim();
4857
+ if (!trimmed) {
4858
+ return false;
4859
+ }
4860
+ const lookupCommand = process.platform === 'win32' ? 'where' : 'which';
4861
+ const result = spawnSync(lookupCommand, [trimmed], {
4862
+ stdio: 'ignore',
4863
+ });
4864
+ return result.status === 0;
4865
+ }
4866
+ function getEnvBrowserCommand(url) {
4867
+ const rawBrowser = process.env.BROWSER?.trim();
4868
+ if (!rawBrowser || rawBrowser.toLowerCase() === 'none') {
4869
+ return null;
4870
+ }
4871
+ const [command, ...rawArgs] = rawBrowser.split(/\s+/);
4872
+ if (!command) {
4873
+ return null;
4874
+ }
4875
+ let replacedUrl = false;
4876
+ const args = rawArgs.map((arg) => {
4877
+ if (!arg.includes('%s')) {
4878
+ return arg;
4879
+ }
4880
+ replacedUrl = true;
4881
+ return arg.replace(/%s/g, url);
4882
+ });
4883
+ if (!replacedUrl) {
4884
+ args.push(url);
4885
+ }
4886
+ return { command, args };
4887
+ }
4888
+ function getDefaultBrowserCommand(url) {
4889
+ if (process.platform === 'darwin') {
4890
+ return { command: 'open', args: [url] };
4891
+ }
4892
+ if (process.platform === 'win32') {
4893
+ return { command: 'rundll32', args: ['url.dll,FileProtocolHandler', url] };
4894
+ }
4895
+ if ((process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) && hasCommand('wslview')) {
4896
+ return { command: 'wslview', args: [url] };
4897
+ }
4898
+ return { command: 'xdg-open', args: [url] };
4899
+ }
4900
+ function getBrowserCommand(url) {
4901
+ return getEnvBrowserCommand(url) || getDefaultBrowserCommand(url);
4902
+ }
4903
+ function hasBrowserEnvironment() {
4904
+ const rawBrowser = process.env.BROWSER?.trim();
4905
+ if (rawBrowser?.toLowerCase() === 'none') {
4906
+ return false;
4907
+ }
4908
+ if (rawBrowser) {
4909
+ return true;
4910
+ }
4911
+ if (process.env.CI) {
4912
+ return false;
4913
+ }
4914
+ if (process.platform === 'linux') {
4915
+ if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) {
4916
+ return true;
4917
+ }
4918
+ return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
4919
+ }
4920
+ return true;
4921
+ }
4922
+ function canOpenUrlInBrowser() {
4923
+ if (!hasBrowserEnvironment()) {
4924
+ return false;
4925
+ }
4926
+ return hasCommand(getBrowserCommand('https://skills.cat').command);
4927
+ }
4928
+ async function openUrlInBrowser(url) {
4929
+ if (!canOpenUrlInBrowser()) {
4930
+ return false;
4931
+ }
4932
+ const browserCommand = getBrowserCommand(url);
4933
+ return new Promise((resolve) => {
4934
+ execFile(browserCommand.command, browserCommand.args, (err) => {
4935
+ resolve(!err);
4936
+ });
4937
+ });
4938
+ }
4939
+
4940
+ class ViewHttpError extends Error {
4941
+ }
4942
+ function normalizeOutputFormat(output) {
4943
+ if (!output) {
4944
+ return null;
4945
+ }
4946
+ const normalized = output.trim().toLowerCase();
4947
+ if (normalized === 'html' || normalized === 'markdown') {
4948
+ return normalized;
4949
+ }
4950
+ return null;
4951
+ }
4952
+ function getSiteBaseUrl() {
4953
+ const registryUrl = getRegistryUrl();
4954
+ try {
4955
+ const parsed = new URL(registryUrl);
4956
+ const pathname = parsed.pathname.replace(/\/+$/, '');
4957
+ const strippedPathname = pathname.endsWith('/registry')
4958
+ ? pathname.slice(0, -'/registry'.length)
4959
+ : pathname.endsWith('/openclaw')
4960
+ ? pathname.slice(0, -'/openclaw'.length)
4961
+ : pathname;
4962
+ return `${parsed.protocol}//${parsed.host}${strippedPathname}`;
4963
+ }
4964
+ catch {
4965
+ return registryUrl.replace(/\/(?:registry|openclaw)\/?$/, '');
4966
+ }
4967
+ }
4968
+ function buildSkillUrl(slug) {
4969
+ return `${getSiteBaseUrl()}${buildSkillPath(slug)}`;
4970
+ }
4971
+ async function buildViewHeaders(output) {
4972
+ const token = await getValidToken();
4973
+ const headers = {
4974
+ 'User-Agent': output === 'markdown'
4975
+ ? 'skillscat-cli/0.1.0 OpenClaw/1.0'
4976
+ : 'skillscat-cli/0.1.0',
4977
+ Accept: output === 'markdown'
4978
+ ? 'text/markdown, text/plain;q=0.9, */*;q=0.8'
4979
+ : 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.8',
4980
+ };
4981
+ if (token) {
4982
+ headers.Authorization = `Bearer ${token}`;
4983
+ }
4984
+ return headers;
4985
+ }
4986
+ async function fetchSkillDocument(slug, output) {
4987
+ const url = buildSkillUrl(slug);
4988
+ const headers = await buildViewHeaders(output);
4989
+ const startTime = Date.now();
4990
+ verboseRequest('GET', url, headers);
4991
+ try {
4992
+ const response = await fetch(url, { headers });
4993
+ verboseResponse(response.status, response.statusText, Date.now() - startTime);
4994
+ if (!response.ok) {
4995
+ const httpError = parseHttpError(response.status, response.statusText);
4996
+ throw new ViewHttpError(httpError.message);
4997
+ }
4998
+ return response.text();
4999
+ }
5000
+ catch (err) {
5001
+ if (!(err instanceof ViewHttpError)) {
5002
+ const networkError = parseNetworkError(err);
5003
+ throw new Error(networkError.message);
5004
+ }
5005
+ throw err;
5006
+ }
5007
+ }
5008
+ function writeOutput(body) {
5009
+ console.log(body);
5010
+ }
5011
+ async function view(slug, options = {}) {
5012
+ try {
5013
+ parseSlug(slug);
5014
+ }
5015
+ catch {
5016
+ error('Invalid slug. Expected format: owner/name');
5017
+ process.exit(1);
5018
+ }
5019
+ if (options.output) {
5020
+ const output = normalizeOutputFormat(options.output);
5021
+ if (!output) {
5022
+ error('Invalid output format. Use `html` or `markdown`.');
5023
+ process.exit(1);
5024
+ }
5025
+ try {
5026
+ const body = await fetchSkillDocument(slug, output);
5027
+ writeOutput(body);
5028
+ return;
5029
+ }
5030
+ catch (err) {
5031
+ error(err instanceof Error ? err.message : 'Failed to fetch skill view');
5032
+ process.exit(1);
5033
+ }
5034
+ }
5035
+ const url = buildSkillUrl(slug);
5036
+ if (canOpenUrlInBrowser()) {
5037
+ const opened = await openUrlInBrowser(url);
5038
+ if (opened) {
5039
+ info$1(`Opened ${slug} in your browser.`);
5040
+ return;
5041
+ }
5042
+ warn('Unable to open a browser automatically. Printing markdown instead.');
5043
+ }
5044
+ else {
5045
+ warn('No browser environment detected. Printing markdown instead.');
5046
+ }
5047
+ try {
5048
+ const markdown = await fetchSkillDocument(slug, 'markdown');
5049
+ writeOutput(markdown);
5050
+ }
5051
+ catch (err) {
5052
+ error(err instanceof Error ? err.message : 'Failed to fetch skill view');
5053
+ process.exit(1);
5054
+ }
5055
+ }
5056
+
4551
5057
  const DEFAULT_REGISTRY_URL = 'https://skills.cat/registry';
4552
5058
  const VALID_KEYS = ['registry'];
4553
5059
  async function configSet(key, value, options = {}) {
@@ -4639,7 +5145,8 @@ program
4639
5145
  .alias('i')
4640
5146
  .description('Add a skill from a repository')
4641
5147
  .option('-g, --global', 'Install to user directory instead of project')
4642
- .option('-a, --agent <agents...>', 'Target specific agents (e.g., claude-code, cursor)')
5148
+ .option('-a, --agent <agents...>', 'Target specific agents (e.g., claude-code, cursor, agents)')
5149
+ .option('-r, --repo', 'Treat <source> as a repository instead of an exact published skill slug')
4643
5150
  .option('-s, --skill <skills...>', 'Install specific skills by name')
4644
5151
  .option('-l, --list', 'List available skills without installing')
4645
5152
  .option('-y, --yes', 'Skip confirmation prompts')
@@ -4671,6 +5178,14 @@ program
4671
5178
  .option('-a, --agent <agents...>', 'Update for specific agents')
4672
5179
  .option('--check', 'Check for updates without installing')
4673
5180
  .action(update);
5181
+ program
5182
+ .command('convert <agent>')
5183
+ .alias('sync')
5184
+ .description('Copy skills from .agents into a specific agent directory')
5185
+ .option('--from <agent>', 'Source agent directory to copy from', 'agents')
5186
+ .option('-g, --global', 'Copy from user directory instead of project')
5187
+ .option('-f, --force', 'Overwrite existing skills in the target directory')
5188
+ .action(convert);
4674
5189
  // Self-upgrade command
4675
5190
  program
4676
5191
  .command('self-upgrade')
@@ -4690,6 +5205,12 @@ program
4690
5205
  .command('info <source>')
4691
5206
  .description('Show detailed information about a skill or repository')
4692
5207
  .action(info);
5208
+ // View command
5209
+ program
5210
+ .command('view <slug>')
5211
+ .description('Open a published skill in the browser or print its rendered output')
5212
+ .option('-o, --output <format>', 'Print `html` or `markdown` to stdout instead of opening a browser')
5213
+ .action(view);
4693
5214
  // Login command
4694
5215
  program
4695
5216
  .command('login')
@@ -4721,6 +5242,12 @@ program
4721
5242
  .command('submit [url]')
4722
5243
  .description('Submit a GitHub repository to SkillsCat registry')
4723
5244
  .action(submit);
5245
+ program
5246
+ .command('report <slug>')
5247
+ .description('Report a skill for security or copyright concerns')
5248
+ .option('-r, --reason <reason>', 'Report reason (security or copyright)')
5249
+ .option('-d, --details <details>', 'Optional report details')
5250
+ .action(report);
4724
5251
  // Unpublish command
4725
5252
  program
4726
5253
  .command('unpublish <slug>')
@@ -3,12 +3,17 @@ export interface Agent {
3
3
  name: string;
4
4
  projectPath: string;
5
5
  globalPath: string;
6
+ aliases?: string[];
6
7
  }
8
+ export declare const FALLBACK_AGENT_ID = "agents";
7
9
  export declare const AGENTS: Agent[];
10
+ export declare function getAgentBasePath(agent: Agent, global: boolean, cwd?: string): string;
8
11
  /**
9
12
  * Detect which agents are installed by checking for their config directories
10
13
  */
11
14
  export declare function detectInstalledAgents(): Agent[];
15
+ export declare function detectProjectAgents(cwd?: string): Agent[];
16
+ export declare function detectPreferredAgents(global: boolean, cwd?: string): Agent[];
12
17
  /**
13
18
  * Get agent by ID
14
19
  */
@@ -17,8 +22,9 @@ export declare function getAgentById(id: string): Agent | undefined;
17
22
  * Get agents by IDs
18
23
  */
19
24
  export declare function getAgentsByIds(ids: string[]): Agent[];
25
+ export declare function getFallbackAgent(): Agent;
20
26
  /**
21
27
  * Get skill installation path for an agent
22
28
  */
23
- export declare function getSkillPath(agent: Agent, skillName: string, global: boolean): string;
29
+ export declare function getSkillPath(agent: Agent, skillName: string, global: boolean, cwd?: string): string;
24
30
  //# sourceMappingURL=agents.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../src/utils/agents/agents.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAmBD,eAAO,MAAM,MAAM,EAAE,KAAK,EAyHzB,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,KAAK,EAAE,CAM/C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAE1D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,CAErD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAGrF"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../src/utils/agents/agents.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAmB1C,eAAO,MAAM,MAAM,EAAE,KAAK,EAgIzB,CAAC;AAYF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,SAAgB,GAAG,MAAM,CAE3F;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,KAAK,EAAE,CAK/C;AAED,wBAAgB,mBAAmB,CAAC,GAAG,SAAgB,GAAG,KAAK,EAAE,CAEhE;AAOD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,SAAgB,GAAG,KAAK,EAAE,CAanF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAK1D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,EAAE,CAErD;AAED,wBAAgB,gBAAgB,IAAI,KAAK,CAMxC;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,SAAgB,GAAG,MAAM,CAG1G"}
@@ -0,0 +1,3 @@
1
+ export declare function canOpenUrlInBrowser(): boolean;
2
+ export declare function openUrlInBrowser(url: string): Promise<boolean>;
3
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/utils/core/browser.ts"],"names":[],"mappings":"AA8FA,wBAAgB,mBAAmB,IAAI,OAAO,CAM7C;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYpE"}
@@ -14,4 +14,16 @@ export declare function parseSlug(slug: string): {
14
14
  * @returns Path segment like "owner/name" (without leading slash)
15
15
  */
16
16
  export declare function slugToPath(slug: string): string;
17
+ /**
18
+ * Encode a slug for use in /skills/[owner]/[...name] paths
19
+ * @param slug - Skill slug in format "owner/name[/nested]"
20
+ * @returns Encoded path segment like "owner/name" (without leading slash)
21
+ */
22
+ export declare function encodeSlugForSkillPath(slug: string): string;
23
+ /**
24
+ * Build the canonical web skill path from a slug
25
+ * @param slug - Skill slug in format "owner/name[/nested]"
26
+ * @returns Path like "/skills/owner/name"
27
+ */
28
+ export declare function buildSkillPath(slug: string): string;
17
29
  //# sourceMappingURL=slug.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../../../src/utils/core/slug.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAMvE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C"}
1
+ {"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../../../src/utils/core/slug.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAMvE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQ3D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD"}
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../src/utils/source/git.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAW1E,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA6BD,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC;IACrC,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACrC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IACnD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC1D,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnE;AAkHD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,GAAG,IAAI,CAKtF;AAwRD;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;CAAE,GACvD,OAAO,CAAC,SAAS,EAAE,CAAC,CAgHtB;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAEvH;AAED,wBAAsB,mCAAmC,CACvD,MAAM,EAAE,UAAU,EAClB,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;CAAE,GACvD,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAmB/B;AAgKD;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAGjG;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAYrD"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../src/utils/source/git.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAW1E,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA6BD,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC;IACrC,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACrC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IACnD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC1D,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnE;AAkHD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,UAAU,GAAG,kBAAkB,GAAG,IAAI,CAKtF;AAwRD;;GAEG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;CAAE,GACvD,OAAO,CAAC,SAAS,EAAE,CAAC,CAgHtB;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAEvH;AAED,wBAAsB,mCAAmC,CACvD,MAAM,EAAE,UAAU,EAClB,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;CAAE,GACvD,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAmB/B;AAmID;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAGjG;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAYrD"}
@@ -1 +1 @@
1
- {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../src/utils/source/source.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3C,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAiF7D;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,UAoBjC,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAgC3E"}
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../src/utils/source/source.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3C,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAiF7D;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,UAqBjC,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAgC3E"}
@@ -12,6 +12,7 @@ export interface InstalledSkill {
12
12
  sha?: string;
13
13
  path: string;
14
14
  contentHash?: string;
15
+ installRoot?: string;
15
16
  }
16
17
  export interface InstalledSkillsDb {
17
18
  version: number;
@@ -28,7 +29,13 @@ export declare function removeInstallation(skillName: string, options?: {
28
29
  source?: RepoSource;
29
30
  agents?: string[];
30
31
  global?: boolean;
32
+ installRoot?: string;
31
33
  }): void;
34
+ export declare function copyInstallationAgent(sourceAgentId: string, targetAgentId: string, options?: {
35
+ global?: boolean;
36
+ installRoot?: string;
37
+ sourceSkillDirs?: string[];
38
+ }): number;
32
39
  /**
33
40
  * Get all installed skills
34
41
  */
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/utils/storage/db.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,UAAU,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AA8HD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAY9D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GACA,IAAI,CA+BN;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CAGrD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAG/E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAUhF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,cAAc,EAAE,CAK/G"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/utils/storage/db.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,UAAU,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AAgND;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAe9D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACA,IAAI,CAmCN;AAED,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B,GACA,MAAM,CAwDR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,EAAE,CAGrD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAG/E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,CAUhF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,cAAc,EAAE,CAK/G"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillscat",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "CLI for installing AI agent skills from SkillsCat registry",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,10 @@
14
14
  "dev": "rollup -c -w",
15
15
  "typecheck": "tsc --noEmit",
16
16
  "prepublishOnly": "npm run build",
17
- "test": "vitest run"
17
+ "test": "pnpm run test:unit",
18
+ "test:unit": "vitest run --config vitest.unit.config.ts",
19
+ "test:preview": "vitest run --config vitest.config.ts",
20
+ "test:all": "pnpm run test:unit && pnpm run test:preview"
18
21
  },
19
22
  "dependencies": {
20
23
  "commander": "^12.1.0",