@localskills/cli 0.1.8 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +82 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1476,7 +1476,7 @@ function detect(projectDir) {
1476
1476
  project: existsSync3(join2(cwd, ".cursor"))
1477
1477
  };
1478
1478
  }
1479
- function resolvePath(slug, scope, projectDir) {
1479
+ function resolvePath(slug, scope, projectDir, _contentType) {
1480
1480
  const safeName = slug.replace(/\//g, "-");
1481
1481
  if (scope === "global") {
1482
1482
  return join2(homedir2(), ".cursor", "rules", `${safeName}.mdc`);
@@ -1537,19 +1537,22 @@ function detect2(projectDir) {
1537
1537
  project: existsSync4(join3(cwd, ".claude"))
1538
1538
  };
1539
1539
  }
1540
- function resolvePath2(slug, scope, projectDir) {
1540
+ function resolvePath2(slug, scope, projectDir, contentType) {
1541
1541
  const safeName = slug.replace(/\//g, "-");
1542
- if (scope === "global") {
1543
- return join3(homedir3(), ".claude", "skills", safeName, "SKILL.md");
1542
+ const base = scope === "global" ? join3(homedir3(), ".claude") : join3(projectDir || process.cwd(), ".claude");
1543
+ if (contentType === "rule") {
1544
+ return join3(base, "rules", `${safeName}.md`);
1544
1545
  }
1545
- const dir = projectDir || process.cwd();
1546
- return join3(dir, ".claude", "skills", safeName, "SKILL.md");
1546
+ return join3(base, "skills", safeName, "SKILL.md");
1547
1547
  }
1548
1548
  function transformContent2(content, skill) {
1549
+ if (skill.type === "rule") {
1550
+ return toPlainMD(content);
1551
+ }
1549
1552
  return toClaudeMD(content, skill);
1550
1553
  }
1551
1554
  function install2(opts) {
1552
- const targetPath = resolvePath2(opts.slug, opts.scope, opts.projectDir);
1555
+ const targetPath = resolvePath2(opts.slug, opts.scope, opts.projectDir, opts.contentType);
1553
1556
  const targetDir = join3(targetPath, "..");
1554
1557
  if (opts.method === "symlink") {
1555
1558
  createSymlink(opts.cachePath, targetPath);
@@ -1665,7 +1668,7 @@ function detect3() {
1665
1668
  }
1666
1669
  return { global: hasCommand, project: hasCommand };
1667
1670
  }
1668
- function resolvePath3(slug, scope, projectDir) {
1671
+ function resolvePath3(slug, scope, projectDir, _contentType) {
1669
1672
  if (scope === "global") {
1670
1673
  return join4(homedir4(), ".codex", "AGENTS.md");
1671
1674
  }
@@ -1718,7 +1721,7 @@ function detect4(projectDir) {
1718
1721
  project: existsSync6(join5(cwd, ".windsurf"))
1719
1722
  };
1720
1723
  }
1721
- function resolvePath4(slug, scope, projectDir) {
1724
+ function resolvePath4(slug, scope, projectDir, _contentType) {
1722
1725
  const safeName = slug.replace(/\//g, "-");
1723
1726
  if (scope === "global") {
1724
1727
  return join5(homedir5(), ".codeium", "windsurf", "memories", "global_rules.md");
@@ -1782,7 +1785,7 @@ function detect5(projectDir) {
1782
1785
  project: existsSync7(join6(cwd, ".clinerules"))
1783
1786
  };
1784
1787
  }
1785
- function resolvePath5(slug, scope, projectDir) {
1788
+ function resolvePath5(slug, scope, projectDir, _contentType) {
1786
1789
  if (scope === "global") {
1787
1790
  throw new Error("Cline does not support global installation");
1788
1791
  }
@@ -1840,7 +1843,7 @@ function detect6(projectDir) {
1840
1843
  project: existsSync8(join7(cwd, ".github"))
1841
1844
  };
1842
1845
  }
1843
- function resolvePath6(slug, scope, projectDir) {
1846
+ function resolvePath6(slug, scope, projectDir, _contentType) {
1844
1847
  if (scope === "global") {
1845
1848
  throw new Error("GitHub Copilot does not support global installation");
1846
1849
  }
@@ -1898,7 +1901,7 @@ function detect7(projectDir) {
1898
1901
  project: hasCommand || existsSync9(join8(cwd, ".opencode"))
1899
1902
  };
1900
1903
  }
1901
- function resolvePath7(slug, scope, projectDir) {
1904
+ function resolvePath7(slug, scope, projectDir, _contentType) {
1902
1905
  const safeName = slug.replace(/\//g, "-");
1903
1906
  if (scope === "global") {
1904
1907
  return join8(homedir6(), ".config", "opencode", "rules", `${safeName}.md`);
@@ -1959,12 +1962,13 @@ function detect8() {
1959
1962
  }
1960
1963
  return { global: false, project: hasCommand };
1961
1964
  }
1962
- function resolvePath8(slug, scope, projectDir) {
1965
+ function resolvePath8(slug, scope, projectDir, contentType) {
1963
1966
  if (scope === "global") {
1964
1967
  throw new Error("Aider does not support global installation");
1965
1968
  }
1966
1969
  const safeName = slug.replace(/\//g, "-");
1967
- return join9(projectDir || process.cwd(), ".aider", "skills", `${safeName}.md`);
1970
+ const subdir = contentType === "rule" ? "rules" : "skills";
1971
+ return join9(projectDir || process.cwd(), ".aider", subdir, `${safeName}.md`);
1968
1972
  }
1969
1973
  function transformContent8(content) {
1970
1974
  return toPlainMD(content);
@@ -1993,7 +1997,7 @@ function removeAiderRead(projectDir, relativePath) {
1993
1997
  writeFileSync8(configPath, filtered.join("\n"));
1994
1998
  }
1995
1999
  function install8(opts) {
1996
- const targetPath = resolvePath8(opts.slug, opts.scope, opts.projectDir);
2000
+ const targetPath = resolvePath8(opts.slug, opts.scope, opts.projectDir, opts.contentType);
1997
2001
  const projectDir = opts.projectDir || process.cwd();
1998
2002
  if (opts.method === "symlink") {
1999
2003
  createSymlink(opts.cachePath, targetPath);
@@ -2002,7 +2006,8 @@ function install8(opts) {
2002
2006
  writeFileSync8(targetPath, opts.content);
2003
2007
  }
2004
2008
  const safeName = opts.slug.replace(/\//g, "-");
2005
- const relativePath = `.aider/skills/${safeName}.md`;
2009
+ const subdir = opts.contentType === "rule" ? "rules" : "skills";
2010
+ const relativePath = `.aider/${subdir}/${safeName}.md`;
2006
2011
  addAiderRead(projectDir, relativePath);
2007
2012
  return targetPath;
2008
2013
  }
@@ -2014,8 +2019,8 @@ function uninstall8(installation, slug) {
2014
2019
  }
2015
2020
  const projectDir = installation.projectDir || process.cwd();
2016
2021
  const safeName = slug.replace(/\//g, "-");
2017
- const relativePath = `.aider/skills/${safeName}.md`;
2018
- removeAiderRead(projectDir, relativePath);
2022
+ removeAiderRead(projectDir, `.aider/skills/${safeName}.md`);
2023
+ removeAiderRead(projectDir, `.aider/rules/${safeName}.md`);
2019
2024
  }
2020
2025
  function defaultMethod8() {
2021
2026
  return "symlink";
@@ -2074,6 +2079,7 @@ function store(slug, content, skill, version) {
2074
2079
  version,
2075
2080
  name: skill.name,
2076
2081
  description: skill.description,
2082
+ type: skill.type ?? "skill",
2077
2083
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
2078
2084
  };
2079
2085
  writeFileSync9(join10(dir, "meta.json"), JSON.stringify(meta, null, 2) + "\n");
@@ -2086,6 +2092,13 @@ function getPlatformFile(slug, platform, skill) {
2086
2092
  if (!raw) throw new Error(`No cached content for ${slug}`);
2087
2093
  const transformed = adapter.transformContent(raw, skill);
2088
2094
  if (platform === "claude") {
2095
+ if (skill.type === "rule") {
2096
+ const claudeRuleDir = join10(dir, "claude-rule");
2097
+ mkdirSync10(claudeRuleDir, { recursive: true });
2098
+ const filePath3 = join10(claudeRuleDir, `${slug.replace(/\//g, "-")}.md`);
2099
+ writeFileSync9(filePath3, transformed);
2100
+ return filePath3;
2101
+ }
2089
2102
  const claudeDir = join10(dir, "claude");
2090
2103
  mkdirSync10(claudeDir, { recursive: true });
2091
2104
  const filePath2 = join10(claudeDir, "SKILL.md");
@@ -2349,7 +2362,9 @@ var installCommand = new Command2("install").description("Install a skill locall
2349
2362
  }
2350
2363
  const { skill, content, version } = res.data;
2351
2364
  spinner.stop(`Fetched ${skill.name} v${version}`);
2352
- store(slug, content, skill, version);
2365
+ const cacheKey = skill.publicId || slug;
2366
+ store(cacheKey, content, skill, version);
2367
+ const contentType = skill.type ?? "skill";
2353
2368
  const installations = [];
2354
2369
  const results = [];
2355
2370
  for (const platformId of platforms) {
@@ -2364,15 +2379,16 @@ var installCommand = new Command2("install").description("Install a skill locall
2364
2379
  continue;
2365
2380
  }
2366
2381
  const actualMethod = adapter.defaultMethod(scope) === "section" ? "section" : method;
2367
- const cachePath = getPlatformFile(slug, platformId, skill);
2382
+ const cachePath = getPlatformFile(cacheKey, platformId, skill);
2368
2383
  const transformed = adapter.transformContent(content, skill);
2369
2384
  const installedPath = adapter.install({
2370
- slug,
2385
+ slug: cacheKey,
2371
2386
  content: transformed,
2372
2387
  scope,
2373
2388
  method: actualMethod,
2374
2389
  cachePath,
2375
- projectDir
2390
+ projectDir,
2391
+ contentType
2376
2392
  });
2377
2393
  const installation = {
2378
2394
  platform: platformId,
@@ -2386,16 +2402,17 @@ var installCommand = new Command2("install").description("Install a skill locall
2386
2402
  const methodLabel = actualMethod === "symlink" ? "symlinked" : actualMethod === "section" ? "section" : "copied";
2387
2403
  results.push(`${desc.name} \u2192 ${installedPath} (${methodLabel})`);
2388
2404
  }
2389
- const existing = config.installed_skills[slug];
2405
+ const existing = config.installed_skills[cacheKey];
2390
2406
  const skillRecord = {
2391
- slug,
2407
+ slug: cacheKey,
2392
2408
  name: skill.name,
2409
+ type: contentType,
2393
2410
  hash: skill.contentHash,
2394
2411
  version,
2395
2412
  cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
2396
2413
  installations: existing ? [...existing.installations, ...installations] : installations
2397
2414
  };
2398
- config.installed_skills[slug] = skillRecord;
2415
+ config.installed_skills[cacheKey] = skillRecord;
2399
2416
  saveConfig(config);
2400
2417
  for (const r of results) {
2401
2418
  R2.success(r);
@@ -2571,6 +2588,8 @@ function scanForSkills(projectDir) {
2571
2588
  scanDirectory(join12(cwd, ".cursor", "rules"), ".mdc", "cursor", "project", results);
2572
2589
  scanClaudeSkills(join12(home, ".claude", "skills"), "global", results);
2573
2590
  scanClaudeSkills(join12(cwd, ".claude", "skills"), "project", results);
2591
+ scanDirectory(join12(home, ".claude", "rules"), ".md", "claude", "global", results, "rule");
2592
+ scanDirectory(join12(cwd, ".claude", "rules"), ".md", "claude", "project", results, "rule");
2574
2593
  scanSingleFile(join12(home, ".codex", "AGENTS.md"), "codex", "global", results);
2575
2594
  scanSingleFile(join12(cwd, "AGENTS.md"), "codex", "project", results);
2576
2595
  scanSingleFile(
@@ -2619,7 +2638,7 @@ function slugFromFilename(filename) {
2619
2638
  function nameFromSlug(slug) {
2620
2639
  return slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2621
2640
  }
2622
- function scanDirectory(dir, ext, platform, scope, results) {
2641
+ function scanDirectory(dir, ext, platform, scope, results, contentType = "skill") {
2623
2642
  if (!existsSync13(dir)) return;
2624
2643
  let entries;
2625
2644
  try {
@@ -2641,7 +2660,8 @@ function scanDirectory(dir, ext, platform, scope, results) {
2641
2660
  scope,
2642
2661
  suggestedName: nameFromSlug(slug),
2643
2662
  suggestedSlug: slug,
2644
- content
2663
+ content,
2664
+ contentType
2645
2665
  });
2646
2666
  } catch {
2647
2667
  }
@@ -2668,7 +2688,8 @@ function scanClaudeSkills(skillsDir, scope, results) {
2668
2688
  scope,
2669
2689
  suggestedName: nameFromSlug(entry),
2670
2690
  suggestedSlug: entry,
2671
- content
2691
+ content,
2692
+ contentType: "skill"
2672
2693
  });
2673
2694
  } catch {
2674
2695
  }
@@ -2698,7 +2719,8 @@ function scanSingleFile(filePath, platform, scope, results) {
2698
2719
  scope,
2699
2720
  suggestedName: nameFromSlug(slug2),
2700
2721
  suggestedSlug: slug2,
2701
- content: content2
2722
+ content: content2,
2723
+ contentType: "skill"
2702
2724
  });
2703
2725
  }
2704
2726
  return;
@@ -2712,7 +2734,8 @@ function scanSingleFile(filePath, platform, scope, results) {
2712
2734
  scope,
2713
2735
  suggestedName: nameFromSlug(slug),
2714
2736
  suggestedSlug: slug,
2715
- content
2737
+ content,
2738
+ contentType: "skill"
2716
2739
  });
2717
2740
  }
2718
2741
 
@@ -2721,7 +2744,7 @@ var publishCommand = new Command6("publish").description("Publish local skill fi
2721
2744
  "--visibility <visibility>",
2722
2745
  "Visibility: public, private, or unlisted",
2723
2746
  "private"
2724
- ).option("-m, --message <message>", "Version message").action(
2747
+ ).option("--type <type>", "Content type: skill or rule", "skill").option("-m, --message <message>", "Version message").action(
2725
2748
  async (fileArg, opts) => {
2726
2749
  const client = new ApiClient();
2727
2750
  if (!client.isAuthenticated()) {
@@ -2754,13 +2777,15 @@ var publishCommand = new Command6("publish").description("Publish local skill fi
2754
2777
  const defaultSlug = basename2(filePath, extname2(filePath));
2755
2778
  const defaultName = defaultSlug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2756
2779
  const skillName = opts.name || defaultName;
2780
+ const contentType = validateContentType(opts.type || "skill");
2757
2781
  const visibility = validateVisibility(opts.visibility || "private");
2758
2782
  const tenantId = await resolveTeam(teams, opts.team);
2759
2783
  await uploadSkill(client, {
2760
2784
  name: skillName,
2761
2785
  content,
2762
2786
  tenantId,
2763
- visibility
2787
+ visibility,
2788
+ type: contentType
2764
2789
  });
2765
2790
  } else {
2766
2791
  We("localskills publish");
@@ -2777,11 +2802,11 @@ var publishCommand = new Command6("publish").description("Publish local skill fi
2777
2802
  return;
2778
2803
  }
2779
2804
  const selected = await je({
2780
- message: "Select skills to publish",
2805
+ message: "Select items to publish",
2781
2806
  options: detected.map((s) => ({
2782
2807
  value: s,
2783
2808
  label: s.suggestedName,
2784
- hint: `${s.platform}/${s.scope} ${shortenPath(s.filePath)}`
2809
+ hint: `${s.platform}/${s.scope}/${s.contentType} ${shortenPath(s.filePath)}`
2785
2810
  })),
2786
2811
  required: true
2787
2812
  });
@@ -2818,11 +2843,24 @@ var publishCommand = new Command6("publish").description("Publish local skill fi
2818
2843
  Ne("Cancelled.");
2819
2844
  process.exit(0);
2820
2845
  }
2846
+ const contentType = await Je({
2847
+ message: "Type?",
2848
+ options: [
2849
+ { value: "skill", label: "Skill", hint: "Reusable agent instructions" },
2850
+ { value: "rule", label: "Rule", hint: "Governance constraints" }
2851
+ ],
2852
+ initialValue: skill.contentType
2853
+ });
2854
+ if (Ct(contentType)) {
2855
+ Ne("Cancelled.");
2856
+ process.exit(0);
2857
+ }
2821
2858
  await uploadSkill(client, {
2822
2859
  name,
2823
2860
  content: skill.content,
2824
2861
  tenantId,
2825
- visibility
2862
+ visibility,
2863
+ type: contentType
2826
2864
  });
2827
2865
  }
2828
2866
  Le("Done!");
@@ -2862,7 +2900,8 @@ async function uploadSkill(client, params) {
2862
2900
  name: params.name,
2863
2901
  content: params.content,
2864
2902
  tenantId: params.tenantId,
2865
- visibility: params.visibility
2903
+ visibility: params.visibility,
2904
+ type: params.type
2866
2905
  });
2867
2906
  if (!res.success || !res.data) {
2868
2907
  spinner.stop(`Failed: ${res.error || "Unknown error"}`);
@@ -2871,6 +2910,13 @@ async function uploadSkill(client, params) {
2871
2910
  spinner.stop(`Published!`);
2872
2911
  R2.success(`\u2192 localskills.sh/s/${res.data.slug}`);
2873
2912
  }
2913
+ function validateContentType(value) {
2914
+ if (value === "skill" || value === "rule") {
2915
+ return value;
2916
+ }
2917
+ console.error(`Invalid type: ${value}. Use skill or rule.`);
2918
+ process.exit(1);
2919
+ }
2874
2920
  function validateVisibility(value) {
2875
2921
  if (value === "public" || value === "private" || value === "unlisted") {
2876
2922
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localskills/cli",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for localskills.sh — install agent skills locally",
5
5
  "type": "module",
6
6
  "bin": {