@localskills/cli 0.1.0 → 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.
Files changed (3) hide show
  1. package/README.md +148 -0
  2. package/dist/index.js +463 -60
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # @localskills/cli
2
+
3
+ CLI for [localskills.sh](https://localskills.sh) — install, manage, and publish agent skills for AI coding tools.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @localskills/cli
9
+ ```
10
+
11
+ ## Supported platforms
12
+
13
+ | Platform | ID | Global | Project | Method |
14
+ |---|---|---|---|---|
15
+ | Cursor | `cursor` | `~/.cursor/rules/` | `.cursor/rules/` | symlink |
16
+ | Claude Code | `claude` | `~/.claude/skills/` | `.claude/skills/` | symlink |
17
+ | Windsurf | `windsurf` | `~/.codeium/windsurf/memories/` | `.windsurf/rules/` | section (global), symlink (project) |
18
+ | Cline | `cline` | — | `.clinerules/` | symlink |
19
+ | GitHub Copilot | `copilot` | — | `.github/copilot-instructions.md` | section |
20
+ | Codex CLI | `codex` | `~/.codex/AGENTS.md` | `./AGENTS.md` | section |
21
+ | OpenCode | `opencode` | `~/.config/opencode/rules/` | `.opencode/rules/` | symlink |
22
+ | Aider | `aider` | — | `.aider/skills/` | symlink |
23
+
24
+ The CLI auto-detects which platforms are installed and pre-selects them during interactive flows.
25
+
26
+ ## Commands
27
+
28
+ ### `localskills login`
29
+
30
+ Authenticate with localskills.sh by pasting your API token.
31
+
32
+ ```bash
33
+ localskills login
34
+ ```
35
+
36
+ ### `localskills logout`
37
+
38
+ Clear stored credentials.
39
+
40
+ ### `localskills whoami`
41
+
42
+ Show the current authenticated user.
43
+
44
+ ### `localskills install [slug]`
45
+
46
+ Install a skill to one or more platforms. Without a slug, launches an interactive picker.
47
+
48
+ ```bash
49
+ # Interactive — prompts for skill, platforms, scope, and method
50
+ localskills install
51
+
52
+ # Direct — skip prompts
53
+ localskills install my-skill -t cursor,claude -g --symlink
54
+ ```
55
+
56
+ **Options:**
57
+
58
+ | Flag | Description |
59
+ |---|---|
60
+ | `-t, --target <targets...>` | Target platforms (comma-separated) |
61
+ | `-g, --global` | Install globally (home directory) |
62
+ | `-p, --project [dir]` | Install in project directory |
63
+ | `--symlink` | Symlink from cache (default) |
64
+ | `--copy` | Copy file instead of symlinking |
65
+
66
+ ### `localskills uninstall [slug]`
67
+
68
+ Remove an installed skill from all platforms. Without a slug, launches an interactive picker.
69
+
70
+ ```bash
71
+ localskills uninstall my-skill --purge
72
+ ```
73
+
74
+ **Options:**
75
+
76
+ | Flag | Description |
77
+ |---|---|
78
+ | `--purge` | Also remove from local cache |
79
+
80
+ ### `localskills pull [slug]`
81
+
82
+ Update installed skills by fetching the latest content from localskills.sh. Without a slug, updates all installed skills.
83
+
84
+ ```bash
85
+ # Update everything
86
+ localskills pull
87
+
88
+ # Update one skill
89
+ localskills pull my-skill
90
+ ```
91
+
92
+ Symlinked installations update automatically when the cache is refreshed. Copied and section-based installations are re-written in place.
93
+
94
+ ### `localskills list`
95
+
96
+ List available skills from localskills.sh.
97
+
98
+ ```bash
99
+ localskills list
100
+ localskills list --public
101
+ ```
102
+
103
+ ### `localskills publish [file]`
104
+
105
+ Publish a local skill file to localskills.sh. Without a file argument, scans your project for unpublished skill files across all supported platform directories.
106
+
107
+ ```bash
108
+ # Scan and publish interactively
109
+ localskills publish
110
+
111
+ # Publish a specific file
112
+ localskills publish .cursor/rules/my-skill.mdc -n "My Skill" --visibility public
113
+ ```
114
+
115
+ **Options:**
116
+
117
+ | Flag | Description |
118
+ |---|---|
119
+ | `-t, --team <id>` | Team ID or slug |
120
+ | `-n, --name <name>` | Skill name |
121
+ | `--visibility <level>` | `private`, `public`, or `unlisted` (default: `private`) |
122
+ | `-m, --message <message>` | Version message |
123
+
124
+ ## Installation methods
125
+
126
+ - **Symlink** (default) — links the skill file from `~/.localskills/cache/` to the target location. Updates propagate automatically via `localskills pull`.
127
+ - **Copy** — writes a standalone copy. Independent of cache but requires re-install to update.
128
+ - **Section** — for single-file platforms (Copilot, Codex, Windsurf global), injects the skill into a shared file between `<!-- localskills:start:slug -->` / `<!-- localskills:end:slug -->` markers.
129
+
130
+ ## Configuration
131
+
132
+ Config and cache are stored at `~/.localskills/`:
133
+
134
+ ```
135
+ ~/.localskills/
136
+ config.json # Auth token, installed skills, preferences
137
+ cache/
138
+ my-skill/
139
+ raw.md # Original content
140
+ meta.json # Hash, version, metadata
141
+ cursor.mdc # Platform-specific transformed files
142
+ claude/
143
+ SKILL.md
144
+ ```
145
+
146
+ ## License
147
+
148
+ MIT
package/dist/index.js CHANGED
@@ -161,7 +161,7 @@ var require_src = __commonJS({
161
161
  });
162
162
 
163
163
  // src/index.ts
164
- import { Command as Command6 } from "commander";
164
+ import { Command as Command7 } from "commander";
165
165
 
166
166
  // src/commands/auth.ts
167
167
  import { Command } from "commander";
@@ -314,10 +314,10 @@ var loginCommand = new Command("login").description("Log in to localskills.sh").
314
314
  input: process.stdin,
315
315
  output: process.stdout
316
316
  });
317
- const token = await new Promise((resolve3) => {
317
+ const token = await new Promise((resolve4) => {
318
318
  rl.question("Paste your API token: ", (answer) => {
319
319
  rl.close();
320
- resolve3(answer.trim());
320
+ resolve4(answer.trim());
321
321
  });
322
322
  });
323
323
  if (!token) {
@@ -345,6 +345,7 @@ var whoamiCommand = new Command("whoami").description("Show current user info").
345
345
  import { Command as Command2 } from "commander";
346
346
 
347
347
  // ../../node_modules/.pnpm/@clack+core@1.0.1/node_modules/@clack/core/dist/index.mjs
348
+ var import_picocolors = __toESM(require_picocolors(), 1);
348
349
  var import_sisteransi = __toESM(require_src(), 1);
349
350
  import { stdout as R, stdin as q } from "process";
350
351
  import * as k from "readline";
@@ -769,9 +770,28 @@ var Wt = class extends x {
769
770
  });
770
771
  }
771
772
  };
773
+ var $t = class extends x {
774
+ get userInputWithCursor() {
775
+ if (this.state === "submit") return this.userInput;
776
+ const e2 = this.userInput;
777
+ if (this.cursor >= e2.length) return `${this.userInput}\u2588`;
778
+ const s = e2.slice(0, this.cursor), [i, ...r] = e2.slice(this.cursor);
779
+ return `${s}${import_picocolors.default.inverse(i)}${r.join("")}`;
780
+ }
781
+ get cursor() {
782
+ return this._cursor;
783
+ }
784
+ constructor(e2) {
785
+ super({ ...e2, initialUserInput: e2.initialUserInput ?? e2.initialValue }), this.on("userInput", (s) => {
786
+ this._setValue(s);
787
+ }), this.on("finalize", () => {
788
+ this.value || (this.value = e2.defaultValue), this.value === void 0 && (this.value = "");
789
+ });
790
+ }
791
+ };
772
792
 
773
793
  // ../../node_modules/.pnpm/@clack+prompts@1.0.1/node_modules/@clack/prompts/dist/index.mjs
774
- var import_picocolors = __toESM(require_picocolors(), 1);
794
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
775
795
  var import_sisteransi2 = __toESM(require_src(), 1);
776
796
  import N2 from "process";
777
797
  import { readdirSync as de, existsSync as $e, lstatSync as xt2 } from "fs";
@@ -812,26 +832,26 @@ var W2 = (t) => {
812
832
  switch (t) {
813
833
  case "initial":
814
834
  case "active":
815
- return import_picocolors.default.cyan(Rt);
835
+ return import_picocolors2.default.cyan(Rt);
816
836
  case "cancel":
817
- return import_picocolors.default.red(dt2);
837
+ return import_picocolors2.default.red(dt2);
818
838
  case "error":
819
- return import_picocolors.default.yellow($t2);
839
+ return import_picocolors2.default.yellow($t2);
820
840
  case "submit":
821
- return import_picocolors.default.green(V);
841
+ return import_picocolors2.default.green(V);
822
842
  }
823
843
  };
824
844
  var vt2 = (t) => {
825
845
  switch (t) {
826
846
  case "initial":
827
847
  case "active":
828
- return import_picocolors.default.cyan(d);
848
+ return import_picocolors2.default.cyan(d);
829
849
  case "cancel":
830
- return import_picocolors.default.red(d);
850
+ return import_picocolors2.default.red(d);
831
851
  case "error":
832
- return import_picocolors.default.yellow(d);
852
+ return import_picocolors2.default.yellow(d);
833
853
  case "submit":
834
- return import_picocolors.default.green(d);
854
+ return import_picocolors2.default.green(d);
835
855
  }
836
856
  };
837
857
  var pe = (t) => t === 161 || t === 164 || t === 167 || t === 168 || t === 170 || t === 173 || t === 174 || t >= 176 && t <= 180 || t >= 182 && t <= 186 || t >= 188 && t <= 191 || t === 198 || t === 208 || t === 215 || t === 216 || t >= 222 && t <= 225 || t === 230 || t >= 232 && t <= 234 || t === 236 || t === 237 || t === 240 || t === 242 || t === 243 || t >= 247 && t <= 250 || t === 252 || t === 254 || t === 257 || t === 273 || t === 275 || t === 283 || t === 294 || t === 295 || t === 299 || t >= 305 && t <= 307 || t === 312 || t >= 319 && t <= 322 || t === 324 || t >= 328 && t <= 331 || t === 333 || t === 338 || t === 339 || t === 358 || t === 359 || t === 363 || t === 462 || t === 464 || t === 466 || t === 468 || t === 470 || t === 472 || t === 474 || t === 476 || t === 593 || t === 609 || t === 708 || t === 711 || t >= 713 && t <= 715 || t === 717 || t === 720 || t >= 728 && t <= 731 || t === 733 || t === 735 || t >= 768 && t <= 879 || t >= 913 && t <= 929 || t >= 931 && t <= 937 || t >= 945 && t <= 961 || t >= 963 && t <= 969 || t === 1025 || t >= 1040 && t <= 1103 || t === 1105 || t === 8208 || t >= 8211 && t <= 8214 || t === 8216 || t === 8217 || t === 8220 || t === 8221 || t >= 8224 && t <= 8226 || t >= 8228 && t <= 8231 || t === 8240 || t === 8242 || t === 8243 || t === 8245 || t === 8251 || t === 8254 || t === 8308 || t === 8319 || t >= 8321 && t <= 8324 || t === 8364 || t === 8451 || t === 8453 || t === 8457 || t === 8467 || t === 8470 || t === 8481 || t === 8482 || t === 8486 || t === 8491 || t === 8531 || t === 8532 || t >= 8539 && t <= 8542 || t >= 8544 && t <= 8555 || t >= 8560 && t <= 8569 || t === 8585 || t >= 8592 && t <= 8601 || t === 8632 || t === 8633 || t === 8658 || t === 8660 || t === 8679 || t === 8704 || t === 8706 || t === 8707 || t === 8711 || t === 8712 || t === 8715 || t === 8719 || t === 8721 || t === 8725 || t === 8730 || t >= 8733 && t <= 8736 || t === 8739 || t === 8741 || t >= 8743 && t <= 8748 || t === 8750 || t >= 8756 && t <= 8759 || t === 8764 || t === 8765 || t === 8776 || t === 8780 || t === 8786 || t === 8800 || t === 8801 || t >= 8804 && t <= 8807 || t === 8810 || t === 8811 || t === 8814 || t === 8815 || t === 8834 || t === 8835 || t === 8838 || t === 8839 || t === 8853 || t === 8857 || t === 8869 || t === 8895 || t === 8978 || t >= 9312 && t <= 9449 || t >= 9451 && t <= 9547 || t >= 9552 && t <= 9587 || t >= 9600 && t <= 9615 || t >= 9618 && t <= 9621 || t === 9632 || t === 9633 || t >= 9635 && t <= 9641 || t === 9650 || t === 9651 || t === 9654 || t === 9655 || t === 9660 || t === 9661 || t === 9664 || t === 9665 || t >= 9670 && t <= 9672 || t === 9675 || t >= 9678 && t <= 9681 || t >= 9698 && t <= 9701 || t === 9711 || t === 9733 || t === 9734 || t === 9737 || t === 9742 || t === 9743 || t === 9756 || t === 9758 || t === 9792 || t === 9794 || t === 9824 || t === 9825 || t >= 9827 && t <= 9829 || t >= 9831 && t <= 9834 || t === 9836 || t === 9837 || t === 9839 || t === 9886 || t === 9887 || t === 9919 || t >= 9926 && t <= 9933 || t >= 9935 && t <= 9939 || t >= 9941 && t <= 9953 || t === 9955 || t === 9960 || t === 9961 || t >= 9963 && t <= 9969 || t === 9972 || t >= 9974 && t <= 9977 || t === 9979 || t === 9980 || t === 9982 || t === 9983 || t === 10045 || t >= 10102 && t <= 10111 || t >= 11094 && t <= 11097 || t >= 12872 && t <= 12879 || t >= 57344 && t <= 63743 || t >= 65024 && t <= 65039 || t === 65533 || t >= 127232 && t <= 127242 || t >= 127248 && t <= 127277 || t >= 127280 && t <= 127337 || t >= 127344 && t <= 127373 || t === 127375 || t === 127376 || t >= 127387 && t <= 127404 || t >= 917760 && t <= 917999 || t >= 983040 && t <= 1048573 || t >= 1048576 && t <= 1114109;
@@ -851,13 +871,13 @@ var jt = (t, r = {}, s = {}) => {
851
871
  if (B2 > I2 || m >= h && m > $) {
852
872
  const _2 = t.slice(I2, B2) || t.slice($, m);
853
873
  y2 = 0;
854
- for (const D of _2.replaceAll(Fe, "")) {
855
- const T2 = D.codePointAt(0) || 0;
874
+ for (const D2 of _2.replaceAll(Fe, "")) {
875
+ const T2 = D2.codePointAt(0) || 0;
856
876
  if (ge(T2) ? w = F : fe(T2) ? w = E : c !== p && pe(T2) ? w = c : w = p, A + w > S2 && (v = Math.min(v, Math.max(I2, $) + y2)), A + w > i) {
857
877
  f = true;
858
878
  break t;
859
879
  }
860
- y2 += D.length, A += w;
880
+ y2 += D2.length, A += w;
861
881
  }
862
882
  I2 = B2 = 0;
863
883
  }
@@ -1009,7 +1029,7 @@ var be = (t, r, s, i, a) => {
1009
1029
  return { lineCount: o, removals: u };
1010
1030
  };
1011
1031
  var X2 = (t) => {
1012
- const { cursor: r, options: s, style: i } = t, a = t.output ?? process.stdout, o = rt(a), u = t.columnPadding ?? 0, l = t.rowPadding ?? 4, n = o - u, c = nt(a), g = import_picocolors.default.dim("..."), F = t.maxItems ?? Number.POSITIVE_INFINITY, p = Math.max(c - l, 0), E = Math.max(Math.min(F, p), 5);
1032
+ const { cursor: r, options: s, style: i } = t, a = t.output ?? process.stdout, o = rt(a), u = t.columnPadding ?? 0, l = t.rowPadding ?? 4, n = o - u, c = nt(a), g = import_picocolors2.default.dim("..."), F = t.maxItems ?? Number.POSITIVE_INFINITY, p = Math.max(c - l, 0), E = Math.max(Math.min(F, p), 5);
1013
1033
  let $ = 0;
1014
1034
  r >= E - 3 && ($ = Math.max(Math.min(r - E + 3, s.length - E), 0));
1015
1035
  let m = E < s.length && $ > 0, h = E < s.length && $ + E < s.length;
@@ -1024,15 +1044,15 @@ var X2 = (t) => {
1024
1044
  }
1025
1045
  if (v > p) {
1026
1046
  let A = 0, w = 0, _2 = v;
1027
- const D = r - S2, T2 = (Y, L2) => be(f, _2, Y, L2, p);
1028
- m ? ({ lineCount: _2, removals: A } = T2(0, D), _2 > p && ({ lineCount: _2, removals: w } = T2(D + 1, f.length))) : ({ lineCount: _2, removals: w } = T2(D + 1, f.length), _2 > p && ({ lineCount: _2, removals: A } = T2(0, D))), A > 0 && (m = true, f.splice(0, A)), w > 0 && (h = true, f.splice(f.length - w, w));
1047
+ const D2 = r - S2, T2 = (Y, L2) => be(f, _2, Y, L2, p);
1048
+ m ? ({ lineCount: _2, removals: A } = T2(0, D2), _2 > p && ({ lineCount: _2, removals: w } = T2(D2 + 1, f.length))) : ({ lineCount: _2, removals: w } = T2(D2 + 1, f.length), _2 > p && ({ lineCount: _2, removals: A } = T2(0, D2))), A > 0 && (m = true, f.splice(0, A)), w > 0 && (h = true, f.splice(f.length - w, w));
1029
1049
  }
1030
1050
  const B2 = [];
1031
1051
  m && B2.push(g);
1032
1052
  for (const A of f) for (const w of A) B2.push(w);
1033
1053
  return h && B2.push(g), B2;
1034
1054
  };
1035
- var R2 = { message: (t = [], { symbol: r = import_picocolors.default.gray(d), secondarySymbol: s = import_picocolors.default.gray(d), output: i = process.stdout, spacing: a = 1, withGuide: o } = {}) => {
1055
+ var R2 = { message: (t = [], { symbol: r = import_picocolors2.default.gray(d), secondarySymbol: s = import_picocolors2.default.gray(d), output: i = process.stdout, spacing: a = 1, withGuide: o } = {}) => {
1036
1056
  const u = [], l = o ?? _.withGuide, n = l ? s : "", c = l ? `${r} ` : "", g = l ? `${s} ` : "";
1037
1057
  for (let p = 0; p < a; p++) u.push(n);
1038
1058
  const F = Array.isArray(t) ? t : t.split(`
@@ -1046,30 +1066,30 @@ var R2 = { message: (t = [], { symbol: r = import_picocolors.default.gray(d), se
1046
1066
  `)}
1047
1067
  `);
1048
1068
  }, info: (t, r) => {
1049
- R2.message(t, { ...r, symbol: import_picocolors.default.blue(ft2) });
1069
+ R2.message(t, { ...r, symbol: import_picocolors2.default.blue(ft2) });
1050
1070
  }, success: (t, r) => {
1051
- R2.message(t, { ...r, symbol: import_picocolors.default.green(Ft2) });
1071
+ R2.message(t, { ...r, symbol: import_picocolors2.default.green(Ft2) });
1052
1072
  }, step: (t, r) => {
1053
- R2.message(t, { ...r, symbol: import_picocolors.default.green(V) });
1073
+ R2.message(t, { ...r, symbol: import_picocolors2.default.green(V) });
1054
1074
  }, warn: (t, r) => {
1055
- R2.message(t, { ...r, symbol: import_picocolors.default.yellow(yt2) });
1075
+ R2.message(t, { ...r, symbol: import_picocolors2.default.yellow(yt2) });
1056
1076
  }, warning: (t, r) => {
1057
1077
  R2.warn(t, r);
1058
1078
  }, error: (t, r) => {
1059
- R2.message(t, { ...r, symbol: import_picocolors.default.red(Et2) });
1079
+ R2.message(t, { ...r, symbol: import_picocolors2.default.red(Et2) });
1060
1080
  } };
1061
1081
  var Ne = (t = "", r) => {
1062
- (r?.output ?? process.stdout).write(`${import_picocolors.default.gray(x2)} ${import_picocolors.default.red(t)}
1082
+ (r?.output ?? process.stdout).write(`${import_picocolors2.default.gray(x2)} ${import_picocolors2.default.red(t)}
1063
1083
 
1064
1084
  `);
1065
1085
  };
1066
1086
  var We = (t = "", r) => {
1067
- (r?.output ?? process.stdout).write(`${import_picocolors.default.gray(ht2)} ${t}
1087
+ (r?.output ?? process.stdout).write(`${import_picocolors2.default.gray(ht2)} ${t}
1068
1088
  `);
1069
1089
  };
1070
1090
  var Le = (t = "", r) => {
1071
- (r?.output ?? process.stdout).write(`${import_picocolors.default.gray(d)}
1072
- ${import_picocolors.default.gray(x2)} ${t}
1091
+ (r?.output ?? process.stdout).write(`${import_picocolors2.default.gray(d)}
1092
+ ${import_picocolors2.default.gray(x2)} ${t}
1073
1093
 
1074
1094
  `);
1075
1095
  };
@@ -1079,13 +1099,13 @@ var Z2 = (t, r) => t.split(`
1079
1099
  var je = (t) => {
1080
1100
  const r = (i, a) => {
1081
1101
  const o = i.label ?? String(i.value);
1082
- return a === "disabled" ? `${import_picocolors.default.gray(q2)} ${Z2(o, (u) => import_picocolors.default.strikethrough(import_picocolors.default.gray(u)))}${i.hint ? ` ${import_picocolors.default.dim(`(${i.hint ?? "disabled"})`)}` : ""}` : a === "active" ? `${import_picocolors.default.cyan(st2)} ${o}${i.hint ? ` ${import_picocolors.default.dim(`(${i.hint})`)}` : ""}` : a === "selected" ? `${import_picocolors.default.green(U2)} ${Z2(o, import_picocolors.default.dim)}${i.hint ? ` ${import_picocolors.default.dim(`(${i.hint})`)}` : ""}` : a === "cancelled" ? `${Z2(o, (u) => import_picocolors.default.strikethrough(import_picocolors.default.dim(u)))}` : a === "active-selected" ? `${import_picocolors.default.green(U2)} ${o}${i.hint ? ` ${import_picocolors.default.dim(`(${i.hint})`)}` : ""}` : a === "submitted" ? `${Z2(o, import_picocolors.default.dim)}` : `${import_picocolors.default.dim(q2)} ${Z2(o, import_picocolors.default.dim)}`;
1102
+ return a === "disabled" ? `${import_picocolors2.default.gray(q2)} ${Z2(o, (u) => import_picocolors2.default.strikethrough(import_picocolors2.default.gray(u)))}${i.hint ? ` ${import_picocolors2.default.dim(`(${i.hint ?? "disabled"})`)}` : ""}` : a === "active" ? `${import_picocolors2.default.cyan(st2)} ${o}${i.hint ? ` ${import_picocolors2.default.dim(`(${i.hint})`)}` : ""}` : a === "selected" ? `${import_picocolors2.default.green(U2)} ${Z2(o, import_picocolors2.default.dim)}${i.hint ? ` ${import_picocolors2.default.dim(`(${i.hint})`)}` : ""}` : a === "cancelled" ? `${Z2(o, (u) => import_picocolors2.default.strikethrough(import_picocolors2.default.dim(u)))}` : a === "active-selected" ? `${import_picocolors2.default.green(U2)} ${o}${i.hint ? ` ${import_picocolors2.default.dim(`(${i.hint})`)}` : ""}` : a === "submitted" ? `${Z2(o, import_picocolors2.default.dim)}` : `${import_picocolors2.default.dim(q2)} ${Z2(o, import_picocolors2.default.dim)}`;
1083
1103
  }, s = t.required ?? true;
1084
1104
  return new Lt({ options: t.options, signal: t.signal, input: t.input, output: t.output, initialValues: t.initialValues, required: s, cursorAt: t.cursorAt, validate(i) {
1085
1105
  if (s && (i === void 0 || i.length === 0)) return `Please select at least one option.
1086
- ${import_picocolors.default.reset(import_picocolors.default.dim(`Press ${import_picocolors.default.gray(import_picocolors.default.bgWhite(import_picocolors.default.inverse(" space ")))} to select, ${import_picocolors.default.gray(import_picocolors.default.bgWhite(import_picocolors.default.inverse(" enter ")))} to submit`))}`;
1106
+ ${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" space ")))} to select, ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" enter ")))} to submit`))}`;
1087
1107
  }, render() {
1088
- const i = xt(t.output, t.message, `${vt2(this.state)} `, `${W2(this.state)} `), a = `${import_picocolors.default.gray(d)}
1108
+ const i = xt(t.output, t.message, `${vt2(this.state)} `, `${W2(this.state)} `), a = `${import_picocolors2.default.gray(d)}
1089
1109
  ${i}
1090
1110
  `, o = this.value ?? [], u = (l, n) => {
1091
1111
  if (l.disabled) return r(l, "disabled");
@@ -1094,19 +1114,19 @@ ${i}
1094
1114
  };
1095
1115
  switch (this.state) {
1096
1116
  case "submit": {
1097
- const l = this.options.filter(({ value: c }) => o.includes(c)).map((c) => r(c, "submitted")).join(import_picocolors.default.dim(", ")) || import_picocolors.default.dim("none"), n = xt(t.output, l, `${import_picocolors.default.gray(d)} `);
1117
+ const l = this.options.filter(({ value: c }) => o.includes(c)).map((c) => r(c, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none"), n = xt(t.output, l, `${import_picocolors2.default.gray(d)} `);
1098
1118
  return `${a}${n}`;
1099
1119
  }
1100
1120
  case "cancel": {
1101
- const l = this.options.filter(({ value: c }) => o.includes(c)).map((c) => r(c, "cancelled")).join(import_picocolors.default.dim(", "));
1102
- if (l.trim() === "") return `${a}${import_picocolors.default.gray(d)}`;
1103
- const n = xt(t.output, l, `${import_picocolors.default.gray(d)} `);
1121
+ const l = this.options.filter(({ value: c }) => o.includes(c)).map((c) => r(c, "cancelled")).join(import_picocolors2.default.dim(", "));
1122
+ if (l.trim() === "") return `${a}${import_picocolors2.default.gray(d)}`;
1123
+ const n = xt(t.output, l, `${import_picocolors2.default.gray(d)} `);
1104
1124
  return `${a}${n}
1105
- ${import_picocolors.default.gray(d)}`;
1125
+ ${import_picocolors2.default.gray(d)}`;
1106
1126
  }
1107
1127
  case "error": {
1108
- const l = `${import_picocolors.default.yellow(d)} `, n = this.error.split(`
1109
- `).map((F, p) => p === 0 ? `${import_picocolors.default.yellow(x2)} ${import_picocolors.default.yellow(F)}` : ` ${F}`).join(`
1128
+ const l = `${import_picocolors2.default.yellow(d)} `, n = this.error.split(`
1129
+ `).map((F, p) => p === 0 ? `${import_picocolors2.default.yellow(x2)} ${import_picocolors2.default.yellow(F)}` : ` ${F}`).join(`
1110
1130
  `), c = a.split(`
1111
1131
  `).length, g = n.split(`
1112
1132
  `).length + 1;
@@ -1116,17 +1136,17 @@ ${n}
1116
1136
  `;
1117
1137
  }
1118
1138
  default: {
1119
- const l = `${import_picocolors.default.cyan(d)} `, n = a.split(`
1139
+ const l = `${import_picocolors2.default.cyan(d)} `, n = a.split(`
1120
1140
  `).length;
1121
1141
  return `${a}${l}${X2({ output: t.output, options: this.options, cursor: this.cursor, maxItems: t.maxItems, columnPadding: l.length, rowPadding: n + 2, style: u }).join(`
1122
1142
  ${l}`)}
1123
- ${import_picocolors.default.cyan(x2)}
1143
+ ${import_picocolors2.default.cyan(x2)}
1124
1144
  `;
1125
1145
  }
1126
1146
  }
1127
1147
  } }).prompt();
1128
1148
  };
1129
- var Ke = import_picocolors.default.magenta;
1149
+ var Ke = import_picocolors2.default.magenta;
1130
1150
  var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, cancelMessage: i, errorMessage: a, frames: o = et2 ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], delay: u = et2 ? 80 : 120, signal: l, ...n } = {}) => {
1131
1151
  const c = ct2();
1132
1152
  let g, F, p = false, E = false, $ = "", m, h = performance.now();
@@ -1144,11 +1164,11 @@ var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, can
1144
1164
  const b = J2(m, y2, { hard: true, trim: false }).split(`
1145
1165
  `);
1146
1166
  b.length > 1 && s.write(import_sisteransi2.cursor.up(b.length - 1)), s.write(import_sisteransi2.cursor.to(0)), s.write(import_sisteransi2.erase.down());
1147
- }, _2 = (b) => b.replace(/\.+$/, ""), D = (b) => {
1167
+ }, _2 = (b) => b.replace(/\.+$/, ""), D2 = (b) => {
1148
1168
  const O2 = (performance.now() - b) / 1e3, j2 = Math.floor(O2 / 60), G2 = Math.floor(O2 % 60);
1149
1169
  return j2 > 0 ? `[${j2}m ${G2}s]` : `[${G2}s]`;
1150
1170
  }, T2 = n.withGuide ?? _.withGuide, Y = (b = "") => {
1151
- p = true, g = Bt({ output: s }), $ = _2(b), h = performance.now(), T2 && s.write(`${import_picocolors.default.gray(d)}
1171
+ p = true, g = Bt({ output: s }), $ = _2(b), h = performance.now(), T2 && s.write(`${import_picocolors2.default.gray(d)}
1152
1172
  `);
1153
1173
  let O2 = 0, j2 = 0;
1154
1174
  B2(), F = setInterval(() => {
@@ -1157,7 +1177,7 @@ var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, can
1157
1177
  const G2 = f(o[O2]);
1158
1178
  let tt2;
1159
1179
  if (c) tt2 = `${G2} ${$}...`;
1160
- else if (t === "timer") tt2 = `${G2} ${$} ${D(h)}`;
1180
+ else if (t === "timer") tt2 = `${G2} ${$} ${D2(h)}`;
1161
1181
  else {
1162
1182
  const te = ".".repeat(Math.floor(j2)).slice(0, 3);
1163
1183
  tt2 = `${G2} ${$}${te}`;
@@ -1168,8 +1188,8 @@ var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, can
1168
1188
  }, L2 = (b = "", O2 = 0, j2 = false) => {
1169
1189
  if (!p) return;
1170
1190
  p = false, clearInterval(F), w();
1171
- const G2 = O2 === 0 ? import_picocolors.default.green(V) : O2 === 1 ? import_picocolors.default.red(dt2) : import_picocolors.default.red($t2);
1172
- $ = b ?? $, j2 || (t === "timer" ? s.write(`${G2} ${$} ${D(h)}
1191
+ const G2 = O2 === 0 ? import_picocolors2.default.green(V) : O2 === 1 ? import_picocolors2.default.red(dt2) : import_picocolors2.default.red($t2);
1192
+ $ = b ?? $, j2 || (t === "timer" ? s.write(`${G2} ${$} ${D2(h)}
1173
1193
  `) : s.write(`${G2} ${$}
1174
1194
  `)), A(), g();
1175
1195
  };
@@ -1189,33 +1209,33 @@ var Je = (t) => {
1189
1209
  const a = s.label ?? String(s.value);
1190
1210
  switch (i) {
1191
1211
  case "disabled":
1192
- return `${import_picocolors.default.gray(H2)} ${lt2(a, import_picocolors.default.gray)}${s.hint ? ` ${import_picocolors.default.dim(`(${s.hint ?? "disabled"})`)}` : ""}`;
1212
+ return `${import_picocolors2.default.gray(H2)} ${lt2(a, import_picocolors2.default.gray)}${s.hint ? ` ${import_picocolors2.default.dim(`(${s.hint ?? "disabled"})`)}` : ""}`;
1193
1213
  case "selected":
1194
- return `${lt2(a, import_picocolors.default.dim)}`;
1214
+ return `${lt2(a, import_picocolors2.default.dim)}`;
1195
1215
  case "active":
1196
- return `${import_picocolors.default.green(Q2)} ${a}${s.hint ? ` ${import_picocolors.default.dim(`(${s.hint})`)}` : ""}`;
1216
+ return `${import_picocolors2.default.green(Q2)} ${a}${s.hint ? ` ${import_picocolors2.default.dim(`(${s.hint})`)}` : ""}`;
1197
1217
  case "cancelled":
1198
- return `${lt2(a, (o) => import_picocolors.default.strikethrough(import_picocolors.default.dim(o)))}`;
1218
+ return `${lt2(a, (o) => import_picocolors2.default.strikethrough(import_picocolors2.default.dim(o)))}`;
1199
1219
  default:
1200
- return `${import_picocolors.default.dim(H2)} ${lt2(a, import_picocolors.default.dim)}`;
1220
+ return `${import_picocolors2.default.dim(H2)} ${lt2(a, import_picocolors2.default.dim)}`;
1201
1221
  }
1202
1222
  };
1203
1223
  return new Wt({ options: t.options, signal: t.signal, input: t.input, output: t.output, initialValue: t.initialValue, render() {
1204
- const s = t.withGuide ?? _.withGuide, i = `${W2(this.state)} `, a = `${vt2(this.state)} `, o = xt(t.output, t.message, a, i), u = `${s ? `${import_picocolors.default.gray(d)}
1224
+ const s = t.withGuide ?? _.withGuide, i = `${W2(this.state)} `, a = `${vt2(this.state)} `, o = xt(t.output, t.message, a, i), u = `${s ? `${import_picocolors2.default.gray(d)}
1205
1225
  ` : ""}${o}
1206
1226
  `;
1207
1227
  switch (this.state) {
1208
1228
  case "submit": {
1209
- const l = s ? `${import_picocolors.default.gray(d)} ` : "", n = xt(t.output, r(this.options[this.cursor], "selected"), l);
1229
+ const l = s ? `${import_picocolors2.default.gray(d)} ` : "", n = xt(t.output, r(this.options[this.cursor], "selected"), l);
1210
1230
  return `${u}${n}`;
1211
1231
  }
1212
1232
  case "cancel": {
1213
- const l = s ? `${import_picocolors.default.gray(d)} ` : "", n = xt(t.output, r(this.options[this.cursor], "cancelled"), l);
1233
+ const l = s ? `${import_picocolors2.default.gray(d)} ` : "", n = xt(t.output, r(this.options[this.cursor], "cancelled"), l);
1214
1234
  return `${u}${n}${s ? `
1215
- ${import_picocolors.default.gray(d)}` : ""}`;
1235
+ ${import_picocolors2.default.gray(d)}` : ""}`;
1216
1236
  }
1217
1237
  default: {
1218
- const l = s ? `${import_picocolors.default.cyan(d)} ` : "", n = s ? import_picocolors.default.cyan(x2) : "", c = u.split(`
1238
+ const l = s ? `${import_picocolors2.default.cyan(d)} ` : "", n = s ? import_picocolors2.default.cyan(x2) : "", c = u.split(`
1219
1239
  `).length, g = s ? 2 : 1;
1220
1240
  return `${u}${l}${X2({ output: t.output, cursor: this.cursor, options: this.options, maxItems: t.maxItems, columnPadding: l.length, rowPadding: c + g, style: (F, p) => r(F, F.disabled ? "disabled" : p ? "active" : "inactive") }).join(`
1221
1241
  ${l}`)}
@@ -1225,7 +1245,36 @@ ${n}
1225
1245
  }
1226
1246
  } }).prompt();
1227
1247
  };
1228
- var Qt = `${import_picocolors.default.gray(d)} `;
1248
+ var Qt = `${import_picocolors2.default.gray(d)} `;
1249
+ var Ze = (t) => new $t({ validate: t.validate, placeholder: t.placeholder, defaultValue: t.defaultValue, initialValue: t.initialValue, output: t.output, signal: t.signal, input: t.input, render() {
1250
+ const r = t?.withGuide ?? _.withGuide, s = `${`${r ? `${import_picocolors2.default.gray(d)}
1251
+ ` : ""}${W2(this.state)} `}${t.message}
1252
+ `, i = t.placeholder ? import_picocolors2.default.inverse(t.placeholder[0]) + import_picocolors2.default.dim(t.placeholder.slice(1)) : import_picocolors2.default.inverse(import_picocolors2.default.hidden("_")), a = this.userInput ? this.userInputWithCursor : i, o = this.value ?? "";
1253
+ switch (this.state) {
1254
+ case "error": {
1255
+ const u = this.error ? ` ${import_picocolors2.default.yellow(this.error)}` : "", l = r ? `${import_picocolors2.default.yellow(d)} ` : "", n = r ? import_picocolors2.default.yellow(x2) : "";
1256
+ return `${s.trim()}
1257
+ ${l}${a}
1258
+ ${n}${u}
1259
+ `;
1260
+ }
1261
+ case "submit": {
1262
+ const u = o ? ` ${import_picocolors2.default.dim(o)}` : "", l = r ? import_picocolors2.default.gray(d) : "";
1263
+ return `${s}${l}${u}`;
1264
+ }
1265
+ case "cancel": {
1266
+ const u = o ? ` ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(o))}` : "", l = r ? import_picocolors2.default.gray(d) : "";
1267
+ return `${s}${l}${u}${o.trim() ? `
1268
+ ${l}` : ""}`;
1269
+ }
1270
+ default: {
1271
+ const u = r ? `${import_picocolors2.default.cyan(d)} ` : "", l = r ? import_picocolors2.default.cyan(x2) : "";
1272
+ return `${s}${u}${a}
1273
+ ${l}
1274
+ `;
1275
+ }
1276
+ }
1277
+ } }).prompt();
1229
1278
 
1230
1279
  // src/lib/cache.ts
1231
1280
  import {
@@ -1275,6 +1324,11 @@ function toClaudeMD(content, skill) {
1275
1324
  function toPlainMD(content) {
1276
1325
  return content;
1277
1326
  }
1327
+ function stripFrontmatter(content) {
1328
+ const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
1329
+ if (match) return content.slice(match[0].length);
1330
+ return content;
1331
+ }
1278
1332
 
1279
1333
  // src/lib/symlink.ts
1280
1334
  import {
@@ -1424,8 +1478,8 @@ function uninstall2(installation, _slug) {
1424
1478
  }
1425
1479
  const parentDir = join3(installation.path, "..");
1426
1480
  try {
1427
- const { readdirSync: readdirSync2 } = __require("fs");
1428
- if (existsSync4(parentDir) && readdirSync2(parentDir).length === 0) {
1481
+ const { readdirSync: readdirSync3 } = __require("fs");
1482
+ if (existsSync4(parentDir) && readdirSync3(parentDir).length === 0) {
1429
1483
  rmSync3(parentDir, { recursive: true });
1430
1484
  }
1431
1485
  } catch {
@@ -1492,6 +1546,17 @@ function removeSection(filePath, slug) {
1492
1546
  writeFileSync4(filePath, result ? result + "\n" : "");
1493
1547
  return true;
1494
1548
  }
1549
+ function listSections(filePath) {
1550
+ if (!existsSync5(filePath)) return [];
1551
+ const content = readFileSync2(filePath, "utf-8");
1552
+ const regex = /<!-- localskills:start:(.+?) -->/g;
1553
+ const slugs = [];
1554
+ let match;
1555
+ while ((match = regex.exec(content)) !== null) {
1556
+ slugs.push(match[1]);
1557
+ }
1558
+ return slugs;
1559
+ }
1495
1560
 
1496
1561
  // src/lib/installers/codex.ts
1497
1562
  var descriptor3 = {
@@ -2386,8 +2451,345 @@ ${transformed}`
2386
2451
  Le(`Pull complete. ${updated} updated, ${skipped} up to date.`);
2387
2452
  });
2388
2453
 
2454
+ // src/commands/publish.ts
2455
+ import { Command as Command6 } from "commander";
2456
+ import { readFileSync as readFileSync6, existsSync as existsSync14 } from "fs";
2457
+ import { resolve as resolve3, basename as basename2, extname as extname2 } from "path";
2458
+
2459
+ // src/lib/scanner.ts
2460
+ import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
2461
+ import { join as join12, basename, extname } from "path";
2462
+ import { homedir as homedir9 } from "os";
2463
+ import { readlinkSync as readlinkSync2, lstatSync as lstatSync2 } from "fs";
2464
+ function scanForSkills(projectDir) {
2465
+ const home = homedir9();
2466
+ const cwd = projectDir || process.cwd();
2467
+ const results = [];
2468
+ scanDirectory(join12(home, ".cursor", "rules"), ".mdc", "cursor", "global", results);
2469
+ scanDirectory(join12(cwd, ".cursor", "rules"), ".mdc", "cursor", "project", results);
2470
+ scanClaudeSkills(join12(home, ".claude", "skills"), "global", results);
2471
+ scanClaudeSkills(join12(cwd, ".claude", "skills"), "project", results);
2472
+ scanSingleFile(join12(home, ".codex", "AGENTS.md"), "codex", "global", results);
2473
+ scanSingleFile(join12(cwd, "AGENTS.md"), "codex", "project", results);
2474
+ scanSingleFile(
2475
+ join12(home, ".codeium", "windsurf", "memories", "global_rules.md"),
2476
+ "windsurf",
2477
+ "global",
2478
+ results
2479
+ );
2480
+ scanDirectory(join12(cwd, ".windsurf", "rules"), ".md", "windsurf", "project", results);
2481
+ scanDirectory(join12(cwd, ".clinerules"), ".md", "cline", "project", results);
2482
+ scanSingleFile(
2483
+ join12(cwd, ".github", "copilot-instructions.md"),
2484
+ "copilot",
2485
+ "project",
2486
+ results
2487
+ );
2488
+ scanDirectory(join12(home, ".config", "opencode", "rules"), ".md", "opencode", "global", results);
2489
+ scanDirectory(join12(cwd, ".opencode", "rules"), ".md", "opencode", "project", results);
2490
+ scanDirectory(join12(cwd, ".aider", "skills"), ".md", "aider", "project", results);
2491
+ return results;
2492
+ }
2493
+ function filterTracked(detected, config) {
2494
+ const trackedPaths = /* @__PURE__ */ new Set();
2495
+ for (const skill of Object.values(config.installed_skills)) {
2496
+ for (const inst of skill.installations) {
2497
+ trackedPaths.add(inst.path);
2498
+ }
2499
+ }
2500
+ const cacheDir = join12(homedir9(), ".localskills", "cache");
2501
+ return detected.filter((skill) => {
2502
+ if (trackedPaths.has(skill.filePath)) return false;
2503
+ try {
2504
+ const stat = lstatSync2(skill.filePath);
2505
+ if (stat.isSymbolicLink()) {
2506
+ const target = readlinkSync2(skill.filePath);
2507
+ if (target.startsWith(cacheDir)) return false;
2508
+ }
2509
+ } catch {
2510
+ }
2511
+ return true;
2512
+ });
2513
+ }
2514
+ function slugFromFilename(filename) {
2515
+ return basename(filename, extname(filename));
2516
+ }
2517
+ function nameFromSlug(slug) {
2518
+ return slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2519
+ }
2520
+ function scanDirectory(dir, ext, platform, scope, results) {
2521
+ if (!existsSync13(dir)) return;
2522
+ let entries;
2523
+ try {
2524
+ entries = readdirSync2(dir);
2525
+ } catch {
2526
+ return;
2527
+ }
2528
+ for (const entry of entries) {
2529
+ if (!entry.endsWith(ext)) continue;
2530
+ const filePath = join12(dir, entry);
2531
+ try {
2532
+ const raw = readFileSync5(filePath, "utf-8");
2533
+ const content = stripFrontmatter(raw).trim();
2534
+ if (!content) continue;
2535
+ const slug = slugFromFilename(entry);
2536
+ results.push({
2537
+ filePath,
2538
+ platform,
2539
+ scope,
2540
+ suggestedName: nameFromSlug(slug),
2541
+ suggestedSlug: slug,
2542
+ content
2543
+ });
2544
+ } catch {
2545
+ }
2546
+ }
2547
+ }
2548
+ function scanClaudeSkills(skillsDir, scope, results) {
2549
+ if (!existsSync13(skillsDir)) return;
2550
+ let entries;
2551
+ try {
2552
+ entries = readdirSync2(skillsDir);
2553
+ } catch {
2554
+ return;
2555
+ }
2556
+ for (const entry of entries) {
2557
+ const skillFile = join12(skillsDir, entry, "SKILL.md");
2558
+ if (!existsSync13(skillFile)) continue;
2559
+ try {
2560
+ const raw = readFileSync5(skillFile, "utf-8");
2561
+ const content = stripFrontmatter(raw).trim();
2562
+ if (!content) continue;
2563
+ results.push({
2564
+ filePath: skillFile,
2565
+ platform: "claude",
2566
+ scope,
2567
+ suggestedName: nameFromSlug(entry),
2568
+ suggestedSlug: entry,
2569
+ content
2570
+ });
2571
+ } catch {
2572
+ }
2573
+ }
2574
+ }
2575
+ function scanSingleFile(filePath, platform, scope, results) {
2576
+ if (!existsSync13(filePath)) return;
2577
+ let raw;
2578
+ try {
2579
+ raw = readFileSync5(filePath, "utf-8");
2580
+ } catch {
2581
+ return;
2582
+ }
2583
+ const sections = listSections(filePath);
2584
+ if (sections.length > 0) {
2585
+ for (const slug2 of sections) {
2586
+ const startMarker = `<!-- localskills:start:${slug2} -->`;
2587
+ const endMarker = `<!-- localskills:end:${slug2} -->`;
2588
+ const startIdx = raw.indexOf(startMarker);
2589
+ const endIdx = raw.indexOf(endMarker);
2590
+ if (startIdx === -1 || endIdx === -1) continue;
2591
+ const content2 = raw.slice(startIdx + startMarker.length, endIdx).trim();
2592
+ if (!content2) continue;
2593
+ results.push({
2594
+ filePath,
2595
+ platform,
2596
+ scope,
2597
+ suggestedName: nameFromSlug(slug2),
2598
+ suggestedSlug: slug2,
2599
+ content: content2
2600
+ });
2601
+ }
2602
+ return;
2603
+ }
2604
+ const content = stripFrontmatter(raw).trim();
2605
+ if (!content) return;
2606
+ const slug = slugFromFilename(filePath);
2607
+ results.push({
2608
+ filePath,
2609
+ platform,
2610
+ scope,
2611
+ suggestedName: nameFromSlug(slug),
2612
+ suggestedSlug: slug,
2613
+ content
2614
+ });
2615
+ }
2616
+
2617
+ // src/commands/publish.ts
2618
+ var publishCommand = new Command6("publish").description("Publish local skill files to localskills.sh").argument("[file]", "Path to a specific file to publish").option("-t, --team <id>", "Team ID to publish to").option("-n, --name <name>", "Skill name").option(
2619
+ "--visibility <visibility>",
2620
+ "Visibility: public, private, or unlisted",
2621
+ "private"
2622
+ ).option("-m, --message <message>", "Version message").action(
2623
+ async (fileArg, opts) => {
2624
+ const client = new ApiClient();
2625
+ if (!client.isAuthenticated()) {
2626
+ console.error("Not authenticated. Run `localskills login` first.");
2627
+ process.exit(1);
2628
+ }
2629
+ const teamsRes = await client.get("/api/tenants");
2630
+ if (!teamsRes.success || !teamsRes.data || teamsRes.data.length === 0) {
2631
+ console.error(
2632
+ "No teams found. Create a team at localskills.sh first."
2633
+ );
2634
+ process.exit(1);
2635
+ return;
2636
+ }
2637
+ const teams = teamsRes.data;
2638
+ if (fileArg) {
2639
+ const filePath = resolve3(fileArg);
2640
+ if (!existsSync14(filePath)) {
2641
+ console.error(`File not found: ${filePath}`);
2642
+ process.exit(1);
2643
+ return;
2644
+ }
2645
+ const raw = readFileSync6(filePath, "utf-8");
2646
+ const content = stripFrontmatter(raw).trim();
2647
+ if (!content) {
2648
+ console.error("File is empty after stripping frontmatter.");
2649
+ process.exit(1);
2650
+ return;
2651
+ }
2652
+ const defaultSlug = basename2(filePath, extname2(filePath));
2653
+ const defaultName = defaultSlug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2654
+ const skillName = opts.name || defaultName;
2655
+ const visibility = validateVisibility(opts.visibility || "private");
2656
+ const tenantId = await resolveTeam(teams, opts.team);
2657
+ await uploadSkill(client, {
2658
+ name: skillName,
2659
+ content,
2660
+ tenantId,
2661
+ visibility
2662
+ });
2663
+ } else {
2664
+ We("localskills publish");
2665
+ const spinner = bt2();
2666
+ spinner.start("Scanning for skills...");
2667
+ const config = loadConfig();
2668
+ const allDetected = scanForSkills();
2669
+ const detected = filterTracked(allDetected, config);
2670
+ spinner.stop(
2671
+ detected.length > 0 ? `Found ${detected.length} skill file${detected.length !== 1 ? "s" : ""}.` : "No unpublished skill files found."
2672
+ );
2673
+ if (detected.length === 0) {
2674
+ Le("Nothing to publish.");
2675
+ return;
2676
+ }
2677
+ const selected = await je({
2678
+ message: "Select skills to publish",
2679
+ options: detected.map((s) => ({
2680
+ value: s,
2681
+ label: s.suggestedName,
2682
+ hint: `${s.platform}/${s.scope} ${shortenPath(s.filePath)}`
2683
+ })),
2684
+ required: true
2685
+ });
2686
+ if (Ct(selected)) {
2687
+ Ne("Cancelled.");
2688
+ process.exit(0);
2689
+ }
2690
+ const skills = selected;
2691
+ const tenantId = await resolveTeam(teams, opts.team);
2692
+ for (const skill of skills) {
2693
+ R2.step(`Publishing ${skill.suggestedName}...`);
2694
+ const name = await Ze({
2695
+ message: "Skill name?",
2696
+ initialValue: skill.suggestedName,
2697
+ validate: (v) => {
2698
+ if (!v || v.length < 1) return "Name is required";
2699
+ if (v.length > 100) return "Name must be 100 characters or less";
2700
+ }
2701
+ });
2702
+ if (Ct(name)) {
2703
+ Ne("Cancelled.");
2704
+ process.exit(0);
2705
+ }
2706
+ const visibility = await Je({
2707
+ message: "Visibility?",
2708
+ options: [
2709
+ { value: "private", label: "Private", hint: "Only team members" },
2710
+ { value: "public", label: "Public", hint: "Anyone can install" },
2711
+ { value: "unlisted", label: "Unlisted", hint: "Accessible via direct link" }
2712
+ ],
2713
+ initialValue: "private"
2714
+ });
2715
+ if (Ct(visibility)) {
2716
+ Ne("Cancelled.");
2717
+ process.exit(0);
2718
+ }
2719
+ await uploadSkill(client, {
2720
+ name,
2721
+ content: skill.content,
2722
+ tenantId,
2723
+ visibility
2724
+ });
2725
+ }
2726
+ Le("Done!");
2727
+ }
2728
+ }
2729
+ );
2730
+ async function resolveTeam(teams, teamFlag) {
2731
+ if (teamFlag) {
2732
+ const match = teams.find((t) => t.id === teamFlag || t.slug === teamFlag);
2733
+ if (!match) {
2734
+ console.error(`Team not found: ${teamFlag}`);
2735
+ process.exit(1);
2736
+ }
2737
+ return match.id;
2738
+ }
2739
+ if (teams.length === 1) {
2740
+ return teams[0].id;
2741
+ }
2742
+ const selected = await Je({
2743
+ message: "Which team?",
2744
+ options: teams.map((t) => ({
2745
+ value: t.id,
2746
+ label: t.name,
2747
+ hint: t.slug
2748
+ }))
2749
+ });
2750
+ if (Ct(selected)) {
2751
+ Ne("Cancelled.");
2752
+ process.exit(0);
2753
+ }
2754
+ return selected;
2755
+ }
2756
+ async function uploadSkill(client, params) {
2757
+ const spinner = bt2();
2758
+ spinner.start(`Uploading ${params.name}...`);
2759
+ const res = await client.post("/api/skills", {
2760
+ name: params.name,
2761
+ content: params.content,
2762
+ tenantId: params.tenantId,
2763
+ visibility: params.visibility
2764
+ });
2765
+ if (!res.success || !res.data) {
2766
+ spinner.stop(`Failed: ${res.error || "Unknown error"}`);
2767
+ return;
2768
+ }
2769
+ spinner.stop(`Published!`);
2770
+ R2.success(`\u2192 localskills.sh/s/${res.data.slug}`);
2771
+ }
2772
+ function validateVisibility(value) {
2773
+ if (value === "public" || value === "private" || value === "unlisted") {
2774
+ return value;
2775
+ }
2776
+ console.error(`Invalid visibility: ${value}. Use public, private, or unlisted.`);
2777
+ process.exit(1);
2778
+ }
2779
+ function shortenPath(filePath) {
2780
+ const home = __require("os").homedir();
2781
+ if (filePath.startsWith(home)) {
2782
+ return "~" + filePath.slice(home.length);
2783
+ }
2784
+ const cwd = process.cwd();
2785
+ if (filePath.startsWith(cwd)) {
2786
+ return "." + filePath.slice(cwd.length);
2787
+ }
2788
+ return filePath;
2789
+ }
2790
+
2389
2791
  // src/index.ts
2390
- var program = new Command6();
2792
+ var program = new Command7();
2391
2793
  program.name("localskills").description("Install and manage agent skills from localskills.sh").version("0.1.0");
2392
2794
  program.addCommand(loginCommand);
2393
2795
  program.addCommand(logoutCommand);
@@ -2396,4 +2798,5 @@ program.addCommand(installCommand);
2396
2798
  program.addCommand(uninstallCommand);
2397
2799
  program.addCommand(listCommand);
2398
2800
  program.addCommand(pullCommand);
2801
+ program.addCommand(publishCommand);
2399
2802
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localskills/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "CLI for localskills.sh — install agent skills locally",
5
5
  "type": "module",
6
6
  "bin": {