@localskills/cli 0.1.0 → 0.1.4

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 +725 -242
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -161,190 +161,15 @@ 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";
168
-
169
- // src/lib/config.ts
170
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
171
- import { join } from "path";
172
- import { homedir } from "os";
173
- var CONFIG_DIR = join(homedir(), ".localskills");
174
- var CONFIG_PATH = join(CONFIG_DIR, "config.json");
175
- var DEFAULT_CONFIG = {
176
- config_version: 2,
177
- api_url: "https://localskills.sh",
178
- token: null,
179
- installed_skills: {},
180
- defaults: {
181
- scope: "project",
182
- method: "symlink"
183
- }
184
- };
185
- function loadConfig() {
186
- if (!existsSync(CONFIG_PATH)) {
187
- return { ...DEFAULT_CONFIG, installed_skills: {} };
188
- }
189
- try {
190
- const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
191
- if (!raw.config_version || raw.config_version < 2) {
192
- return migrateV1toV2(raw);
193
- }
194
- return { ...DEFAULT_CONFIG, ...raw };
195
- } catch {
196
- return { ...DEFAULT_CONFIG, installed_skills: {} };
197
- }
198
- }
199
- function saveConfig(config) {
200
- mkdirSync(CONFIG_DIR, { recursive: true });
201
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
202
- }
203
- function migrateV1toV2(v1) {
204
- const v2 = {
205
- config_version: 2,
206
- api_url: v1.api_url || DEFAULT_CONFIG.api_url,
207
- token: v1.token,
208
- installed_skills: {},
209
- defaults: {
210
- scope: "project",
211
- method: "symlink"
212
- }
213
- };
214
- for (const [key, skill] of Object.entries(v1.installed_skills || {})) {
215
- const isGlobal = skill.path.startsWith(homedir());
216
- v2.installed_skills[key] = {
217
- slug: skill.slug,
218
- name: skill.slug,
219
- hash: skill.hash,
220
- version: 0,
221
- cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
222
- installations: [
223
- {
224
- platform: skill.target,
225
- scope: isGlobal ? "global" : "project",
226
- method: "copy",
227
- path: skill.path,
228
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
229
- }
230
- ]
231
- };
232
- }
233
- saveConfig(v2);
234
- return v2;
235
- }
236
- function getToken() {
237
- return loadConfig().token;
238
- }
239
- function setToken(token) {
240
- const config = loadConfig();
241
- config.token = token;
242
- saveConfig(config);
243
- }
244
- function clearToken() {
245
- const config = loadConfig();
246
- config.token = null;
247
- saveConfig(config);
248
- }
249
-
250
- // src/lib/api-client.ts
251
- var ApiClient = class {
252
- baseUrl;
253
- token;
254
- constructor() {
255
- const config = loadConfig();
256
- this.baseUrl = config.api_url;
257
- this.token = config.token;
258
- }
259
- headers() {
260
- const h = {
261
- "Content-Type": "application/json"
262
- };
263
- if (this.token) {
264
- h["Authorization"] = `Bearer ${this.token}`;
265
- }
266
- return h;
267
- }
268
- async get(path) {
269
- const res = await fetch(`${this.baseUrl}${path}`, {
270
- headers: this.headers()
271
- });
272
- return res.json();
273
- }
274
- async post(path, body) {
275
- const res = await fetch(`${this.baseUrl}${path}`, {
276
- method: "POST",
277
- headers: this.headers(),
278
- body: body ? JSON.stringify(body) : void 0
279
- });
280
- return res.json();
281
- }
282
- async put(path, body) {
283
- const res = await fetch(`${this.baseUrl}${path}`, {
284
- method: "PUT",
285
- headers: this.headers(),
286
- body: JSON.stringify(body)
287
- });
288
- return res.json();
289
- }
290
- async delete(path) {
291
- const res = await fetch(`${this.baseUrl}${path}`, {
292
- method: "DELETE",
293
- headers: this.headers()
294
- });
295
- return res.json();
296
- }
297
- async getRaw(path) {
298
- return fetch(`${this.baseUrl}${path}`, {
299
- headers: this.headers()
300
- });
301
- }
302
- isAuthenticated() {
303
- return this.token !== null;
304
- }
305
- };
306
-
307
- // src/commands/auth.ts
308
- var loginCommand = new Command("login").description("Log in to localskills.sh").action(async () => {
309
- const client = new ApiClient();
310
- console.log("Opening browser for authentication...");
311
- console.log("Visit: https://localskills.sh/api/cli/auth to authorize this device\n");
312
- const readline = await import("readline");
313
- const rl = readline.createInterface({
314
- input: process.stdin,
315
- output: process.stdout
316
- });
317
- const token = await new Promise((resolve3) => {
318
- rl.question("Paste your API token: ", (answer) => {
319
- rl.close();
320
- resolve3(answer.trim());
321
- });
322
- });
323
- if (!token) {
324
- console.error("No token provided. Login cancelled.");
325
- process.exit(1);
326
- }
327
- setToken(token);
328
- console.log("Logged in successfully!");
329
- });
330
- var logoutCommand = new Command("logout").description("Log out of localskills.sh").action(() => {
331
- clearToken();
332
- console.log("Logged out.");
333
- });
334
- var whoamiCommand = new Command("whoami").description("Show current user info").action(async () => {
335
- const token = getToken();
336
- if (!token) {
337
- console.log("Not logged in. Run `localskills login` to authenticate.");
338
- process.exit(1);
339
- return;
340
- }
341
- console.log("Authenticated with token:", token.slice(0, 8) + "...");
342
- });
343
-
344
- // src/commands/install.ts
345
- import { Command as Command2 } from "commander";
168
+ import { randomBytes, createHash } from "crypto";
169
+ import { execSync } from "child_process";
346
170
 
347
171
  // ../../node_modules/.pnpm/@clack+core@1.0.1/node_modules/@clack/core/dist/index.mjs
172
+ var import_picocolors = __toESM(require_picocolors(), 1);
348
173
  var import_sisteransi = __toESM(require_src(), 1);
349
174
  import { stdout as R, stdin as q } from "process";
350
175
  import * as k from "readline";
@@ -769,9 +594,28 @@ var Wt = class extends x {
769
594
  });
770
595
  }
771
596
  };
597
+ var $t = class extends x {
598
+ get userInputWithCursor() {
599
+ if (this.state === "submit") return this.userInput;
600
+ const e2 = this.userInput;
601
+ if (this.cursor >= e2.length) return `${this.userInput}\u2588`;
602
+ const s = e2.slice(0, this.cursor), [i, ...r] = e2.slice(this.cursor);
603
+ return `${s}${import_picocolors.default.inverse(i)}${r.join("")}`;
604
+ }
605
+ get cursor() {
606
+ return this._cursor;
607
+ }
608
+ constructor(e2) {
609
+ super({ ...e2, initialUserInput: e2.initialUserInput ?? e2.initialValue }), this.on("userInput", (s) => {
610
+ this._setValue(s);
611
+ }), this.on("finalize", () => {
612
+ this.value || (this.value = e2.defaultValue), this.value === void 0 && (this.value = "");
613
+ });
614
+ }
615
+ };
772
616
 
773
617
  // ../../node_modules/.pnpm/@clack+prompts@1.0.1/node_modules/@clack/prompts/dist/index.mjs
774
- var import_picocolors = __toESM(require_picocolors(), 1);
618
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
775
619
  var import_sisteransi2 = __toESM(require_src(), 1);
776
620
  import N2 from "process";
777
621
  import { readdirSync as de, existsSync as $e, lstatSync as xt2 } from "fs";
@@ -812,26 +656,26 @@ var W2 = (t) => {
812
656
  switch (t) {
813
657
  case "initial":
814
658
  case "active":
815
- return import_picocolors.default.cyan(Rt);
659
+ return import_picocolors2.default.cyan(Rt);
816
660
  case "cancel":
817
- return import_picocolors.default.red(dt2);
661
+ return import_picocolors2.default.red(dt2);
818
662
  case "error":
819
- return import_picocolors.default.yellow($t2);
663
+ return import_picocolors2.default.yellow($t2);
820
664
  case "submit":
821
- return import_picocolors.default.green(V);
665
+ return import_picocolors2.default.green(V);
822
666
  }
823
667
  };
824
668
  var vt2 = (t) => {
825
669
  switch (t) {
826
670
  case "initial":
827
671
  case "active":
828
- return import_picocolors.default.cyan(d);
672
+ return import_picocolors2.default.cyan(d);
829
673
  case "cancel":
830
- return import_picocolors.default.red(d);
674
+ return import_picocolors2.default.red(d);
831
675
  case "error":
832
- return import_picocolors.default.yellow(d);
676
+ return import_picocolors2.default.yellow(d);
833
677
  case "submit":
834
- return import_picocolors.default.green(d);
678
+ return import_picocolors2.default.green(d);
835
679
  }
836
680
  };
837
681
  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 +695,13 @@ var jt = (t, r = {}, s = {}) => {
851
695
  if (B2 > I2 || m >= h && m > $) {
852
696
  const _2 = t.slice(I2, B2) || t.slice($, m);
853
697
  y2 = 0;
854
- for (const D of _2.replaceAll(Fe, "")) {
855
- const T2 = D.codePointAt(0) || 0;
698
+ for (const D2 of _2.replaceAll(Fe, "")) {
699
+ const T2 = D2.codePointAt(0) || 0;
856
700
  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
701
  f = true;
858
702
  break t;
859
703
  }
860
- y2 += D.length, A += w;
704
+ y2 += D2.length, A += w;
861
705
  }
862
706
  I2 = B2 = 0;
863
707
  }
@@ -1009,7 +853,7 @@ var be = (t, r, s, i, a) => {
1009
853
  return { lineCount: o, removals: u };
1010
854
  };
1011
855
  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);
856
+ 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
857
  let $ = 0;
1014
858
  r >= E - 3 && ($ = Math.max(Math.min(r - E + 3, s.length - E), 0));
1015
859
  let m = E < s.length && $ > 0, h = E < s.length && $ + E < s.length;
@@ -1024,15 +868,15 @@ var X2 = (t) => {
1024
868
  }
1025
869
  if (v > p) {
1026
870
  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));
871
+ const D2 = r - S2, T2 = (Y, L2) => be(f, _2, Y, L2, p);
872
+ 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
873
  }
1030
874
  const B2 = [];
1031
875
  m && B2.push(g);
1032
876
  for (const A of f) for (const w of A) B2.push(w);
1033
877
  return h && B2.push(g), B2;
1034
878
  };
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 } = {}) => {
879
+ 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
880
  const u = [], l = o ?? _.withGuide, n = l ? s : "", c = l ? `${r} ` : "", g = l ? `${s} ` : "";
1037
881
  for (let p = 0; p < a; p++) u.push(n);
1038
882
  const F = Array.isArray(t) ? t : t.split(`
@@ -1046,30 +890,30 @@ var R2 = { message: (t = [], { symbol: r = import_picocolors.default.gray(d), se
1046
890
  `)}
1047
891
  `);
1048
892
  }, info: (t, r) => {
1049
- R2.message(t, { ...r, symbol: import_picocolors.default.blue(ft2) });
893
+ R2.message(t, { ...r, symbol: import_picocolors2.default.blue(ft2) });
1050
894
  }, success: (t, r) => {
1051
- R2.message(t, { ...r, symbol: import_picocolors.default.green(Ft2) });
895
+ R2.message(t, { ...r, symbol: import_picocolors2.default.green(Ft2) });
1052
896
  }, step: (t, r) => {
1053
- R2.message(t, { ...r, symbol: import_picocolors.default.green(V) });
897
+ R2.message(t, { ...r, symbol: import_picocolors2.default.green(V) });
1054
898
  }, warn: (t, r) => {
1055
- R2.message(t, { ...r, symbol: import_picocolors.default.yellow(yt2) });
899
+ R2.message(t, { ...r, symbol: import_picocolors2.default.yellow(yt2) });
1056
900
  }, warning: (t, r) => {
1057
901
  R2.warn(t, r);
1058
902
  }, error: (t, r) => {
1059
- R2.message(t, { ...r, symbol: import_picocolors.default.red(Et2) });
903
+ R2.message(t, { ...r, symbol: import_picocolors2.default.red(Et2) });
1060
904
  } };
1061
905
  var Ne = (t = "", r) => {
1062
- (r?.output ?? process.stdout).write(`${import_picocolors.default.gray(x2)} ${import_picocolors.default.red(t)}
906
+ (r?.output ?? process.stdout).write(`${import_picocolors2.default.gray(x2)} ${import_picocolors2.default.red(t)}
1063
907
 
1064
908
  `);
1065
909
  };
1066
910
  var We = (t = "", r) => {
1067
- (r?.output ?? process.stdout).write(`${import_picocolors.default.gray(ht2)} ${t}
911
+ (r?.output ?? process.stdout).write(`${import_picocolors2.default.gray(ht2)} ${t}
1068
912
  `);
1069
913
  };
1070
914
  var Le = (t = "", r) => {
1071
- (r?.output ?? process.stdout).write(`${import_picocolors.default.gray(d)}
1072
- ${import_picocolors.default.gray(x2)} ${t}
915
+ (r?.output ?? process.stdout).write(`${import_picocolors2.default.gray(d)}
916
+ ${import_picocolors2.default.gray(x2)} ${t}
1073
917
 
1074
918
  `);
1075
919
  };
@@ -1079,13 +923,13 @@ var Z2 = (t, r) => t.split(`
1079
923
  var je = (t) => {
1080
924
  const r = (i, a) => {
1081
925
  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)}`;
926
+ 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
927
  }, s = t.required ?? true;
1084
928
  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
929
  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`))}`;
930
+ ${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
931
  }, render() {
1088
- const i = xt(t.output, t.message, `${vt2(this.state)} `, `${W2(this.state)} `), a = `${import_picocolors.default.gray(d)}
932
+ const i = xt(t.output, t.message, `${vt2(this.state)} `, `${W2(this.state)} `), a = `${import_picocolors2.default.gray(d)}
1089
933
  ${i}
1090
934
  `, o = this.value ?? [], u = (l, n) => {
1091
935
  if (l.disabled) return r(l, "disabled");
@@ -1094,19 +938,19 @@ ${i}
1094
938
  };
1095
939
  switch (this.state) {
1096
940
  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)} `);
941
+ 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
942
  return `${a}${n}`;
1099
943
  }
1100
944
  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)} `);
945
+ const l = this.options.filter(({ value: c }) => o.includes(c)).map((c) => r(c, "cancelled")).join(import_picocolors2.default.dim(", "));
946
+ if (l.trim() === "") return `${a}${import_picocolors2.default.gray(d)}`;
947
+ const n = xt(t.output, l, `${import_picocolors2.default.gray(d)} `);
1104
948
  return `${a}${n}
1105
- ${import_picocolors.default.gray(d)}`;
949
+ ${import_picocolors2.default.gray(d)}`;
1106
950
  }
1107
951
  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(`
952
+ const l = `${import_picocolors2.default.yellow(d)} `, n = this.error.split(`
953
+ `).map((F, p) => p === 0 ? `${import_picocolors2.default.yellow(x2)} ${import_picocolors2.default.yellow(F)}` : ` ${F}`).join(`
1110
954
  `), c = a.split(`
1111
955
  `).length, g = n.split(`
1112
956
  `).length + 1;
@@ -1116,17 +960,17 @@ ${n}
1116
960
  `;
1117
961
  }
1118
962
  default: {
1119
- const l = `${import_picocolors.default.cyan(d)} `, n = a.split(`
963
+ const l = `${import_picocolors2.default.cyan(d)} `, n = a.split(`
1120
964
  `).length;
1121
965
  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
966
  ${l}`)}
1123
- ${import_picocolors.default.cyan(x2)}
967
+ ${import_picocolors2.default.cyan(x2)}
1124
968
  `;
1125
969
  }
1126
970
  }
1127
971
  } }).prompt();
1128
972
  };
1129
- var Ke = import_picocolors.default.magenta;
973
+ var Ke = import_picocolors2.default.magenta;
1130
974
  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
975
  const c = ct2();
1132
976
  let g, F, p = false, E = false, $ = "", m, h = performance.now();
@@ -1144,11 +988,11 @@ var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, can
1144
988
  const b = J2(m, y2, { hard: true, trim: false }).split(`
1145
989
  `);
1146
990
  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) => {
991
+ }, _2 = (b) => b.replace(/\.+$/, ""), D2 = (b) => {
1148
992
  const O2 = (performance.now() - b) / 1e3, j2 = Math.floor(O2 / 60), G2 = Math.floor(O2 % 60);
1149
993
  return j2 > 0 ? `[${j2}m ${G2}s]` : `[${G2}s]`;
1150
994
  }, 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)}
995
+ p = true, g = Bt({ output: s }), $ = _2(b), h = performance.now(), T2 && s.write(`${import_picocolors2.default.gray(d)}
1152
996
  `);
1153
997
  let O2 = 0, j2 = 0;
1154
998
  B2(), F = setInterval(() => {
@@ -1157,7 +1001,7 @@ var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, can
1157
1001
  const G2 = f(o[O2]);
1158
1002
  let tt2;
1159
1003
  if (c) tt2 = `${G2} ${$}...`;
1160
- else if (t === "timer") tt2 = `${G2} ${$} ${D(h)}`;
1004
+ else if (t === "timer") tt2 = `${G2} ${$} ${D2(h)}`;
1161
1005
  else {
1162
1006
  const te = ".".repeat(Math.floor(j2)).slice(0, 3);
1163
1007
  tt2 = `${G2} ${$}${te}`;
@@ -1168,8 +1012,8 @@ var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, can
1168
1012
  }, L2 = (b = "", O2 = 0, j2 = false) => {
1169
1013
  if (!p) return;
1170
1014
  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)}
1015
+ const G2 = O2 === 0 ? import_picocolors2.default.green(V) : O2 === 1 ? import_picocolors2.default.red(dt2) : import_picocolors2.default.red($t2);
1016
+ $ = b ?? $, j2 || (t === "timer" ? s.write(`${G2} ${$} ${D2(h)}
1173
1017
  `) : s.write(`${G2} ${$}
1174
1018
  `)), A(), g();
1175
1019
  };
@@ -1189,33 +1033,33 @@ var Je = (t) => {
1189
1033
  const a = s.label ?? String(s.value);
1190
1034
  switch (i) {
1191
1035
  case "disabled":
1192
- return `${import_picocolors.default.gray(H2)} ${lt2(a, import_picocolors.default.gray)}${s.hint ? ` ${import_picocolors.default.dim(`(${s.hint ?? "disabled"})`)}` : ""}`;
1036
+ return `${import_picocolors2.default.gray(H2)} ${lt2(a, import_picocolors2.default.gray)}${s.hint ? ` ${import_picocolors2.default.dim(`(${s.hint ?? "disabled"})`)}` : ""}`;
1193
1037
  case "selected":
1194
- return `${lt2(a, import_picocolors.default.dim)}`;
1038
+ return `${lt2(a, import_picocolors2.default.dim)}`;
1195
1039
  case "active":
1196
- return `${import_picocolors.default.green(Q2)} ${a}${s.hint ? ` ${import_picocolors.default.dim(`(${s.hint})`)}` : ""}`;
1040
+ return `${import_picocolors2.default.green(Q2)} ${a}${s.hint ? ` ${import_picocolors2.default.dim(`(${s.hint})`)}` : ""}`;
1197
1041
  case "cancelled":
1198
- return `${lt2(a, (o) => import_picocolors.default.strikethrough(import_picocolors.default.dim(o)))}`;
1042
+ return `${lt2(a, (o) => import_picocolors2.default.strikethrough(import_picocolors2.default.dim(o)))}`;
1199
1043
  default:
1200
- return `${import_picocolors.default.dim(H2)} ${lt2(a, import_picocolors.default.dim)}`;
1044
+ return `${import_picocolors2.default.dim(H2)} ${lt2(a, import_picocolors2.default.dim)}`;
1201
1045
  }
1202
1046
  };
1203
1047
  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)}
1048
+ 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
1049
  ` : ""}${o}
1206
1050
  `;
1207
1051
  switch (this.state) {
1208
1052
  case "submit": {
1209
- const l = s ? `${import_picocolors.default.gray(d)} ` : "", n = xt(t.output, r(this.options[this.cursor], "selected"), l);
1053
+ const l = s ? `${import_picocolors2.default.gray(d)} ` : "", n = xt(t.output, r(this.options[this.cursor], "selected"), l);
1210
1054
  return `${u}${n}`;
1211
1055
  }
1212
1056
  case "cancel": {
1213
- const l = s ? `${import_picocolors.default.gray(d)} ` : "", n = xt(t.output, r(this.options[this.cursor], "cancelled"), l);
1057
+ const l = s ? `${import_picocolors2.default.gray(d)} ` : "", n = xt(t.output, r(this.options[this.cursor], "cancelled"), l);
1214
1058
  return `${u}${n}${s ? `
1215
- ${import_picocolors.default.gray(d)}` : ""}`;
1059
+ ${import_picocolors2.default.gray(d)}` : ""}`;
1216
1060
  }
1217
1061
  default: {
1218
- const l = s ? `${import_picocolors.default.cyan(d)} ` : "", n = s ? import_picocolors.default.cyan(x2) : "", c = u.split(`
1062
+ const l = s ? `${import_picocolors2.default.cyan(d)} ` : "", n = s ? import_picocolors2.default.cyan(x2) : "", c = u.split(`
1219
1063
  `).length, g = s ? 2 : 1;
1220
1064
  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
1065
  ${l}`)}
@@ -1225,7 +1069,292 @@ ${n}
1225
1069
  }
1226
1070
  } }).prompt();
1227
1071
  };
1228
- var Qt = `${import_picocolors.default.gray(d)} `;
1072
+ var Qt = `${import_picocolors2.default.gray(d)} `;
1073
+ 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() {
1074
+ const r = t?.withGuide ?? _.withGuide, s = `${`${r ? `${import_picocolors2.default.gray(d)}
1075
+ ` : ""}${W2(this.state)} `}${t.message}
1076
+ `, 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 ?? "";
1077
+ switch (this.state) {
1078
+ case "error": {
1079
+ const u = this.error ? ` ${import_picocolors2.default.yellow(this.error)}` : "", l = r ? `${import_picocolors2.default.yellow(d)} ` : "", n = r ? import_picocolors2.default.yellow(x2) : "";
1080
+ return `${s.trim()}
1081
+ ${l}${a}
1082
+ ${n}${u}
1083
+ `;
1084
+ }
1085
+ case "submit": {
1086
+ const u = o ? ` ${import_picocolors2.default.dim(o)}` : "", l = r ? import_picocolors2.default.gray(d) : "";
1087
+ return `${s}${l}${u}`;
1088
+ }
1089
+ case "cancel": {
1090
+ const u = o ? ` ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(o))}` : "", l = r ? import_picocolors2.default.gray(d) : "";
1091
+ return `${s}${l}${u}${o.trim() ? `
1092
+ ${l}` : ""}`;
1093
+ }
1094
+ default: {
1095
+ const u = r ? `${import_picocolors2.default.cyan(d)} ` : "", l = r ? import_picocolors2.default.cyan(x2) : "";
1096
+ return `${s}${u}${a}
1097
+ ${l}
1098
+ `;
1099
+ }
1100
+ }
1101
+ } }).prompt();
1102
+
1103
+ // src/lib/config.ts
1104
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
1105
+ import { join } from "path";
1106
+ import { homedir } from "os";
1107
+ var CONFIG_DIR = join(homedir(), ".localskills");
1108
+ var CONFIG_PATH = join(CONFIG_DIR, "config.json");
1109
+ var DEFAULT_CONFIG = {
1110
+ config_version: 2,
1111
+ api_url: "https://localskills.sh",
1112
+ token: null,
1113
+ installed_skills: {},
1114
+ defaults: {
1115
+ scope: "project",
1116
+ method: "symlink"
1117
+ }
1118
+ };
1119
+ function loadConfig() {
1120
+ if (!existsSync(CONFIG_PATH)) {
1121
+ return { ...DEFAULT_CONFIG, installed_skills: {} };
1122
+ }
1123
+ try {
1124
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1125
+ if (!raw.config_version || raw.config_version < 2) {
1126
+ return migrateV1toV2(raw);
1127
+ }
1128
+ return { ...DEFAULT_CONFIG, ...raw };
1129
+ } catch {
1130
+ return { ...DEFAULT_CONFIG, installed_skills: {} };
1131
+ }
1132
+ }
1133
+ function saveConfig(config) {
1134
+ mkdirSync(CONFIG_DIR, { recursive: true });
1135
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
1136
+ }
1137
+ function migrateV1toV2(v1) {
1138
+ const v2 = {
1139
+ config_version: 2,
1140
+ api_url: v1.api_url || DEFAULT_CONFIG.api_url,
1141
+ token: v1.token,
1142
+ installed_skills: {},
1143
+ defaults: {
1144
+ scope: "project",
1145
+ method: "symlink"
1146
+ }
1147
+ };
1148
+ for (const [key, skill] of Object.entries(v1.installed_skills || {})) {
1149
+ const isGlobal = skill.path.startsWith(homedir());
1150
+ v2.installed_skills[key] = {
1151
+ slug: skill.slug,
1152
+ name: skill.slug,
1153
+ hash: skill.hash,
1154
+ version: 0,
1155
+ cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
1156
+ installations: [
1157
+ {
1158
+ platform: skill.target,
1159
+ scope: isGlobal ? "global" : "project",
1160
+ method: "copy",
1161
+ path: skill.path,
1162
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
1163
+ }
1164
+ ]
1165
+ };
1166
+ }
1167
+ saveConfig(v2);
1168
+ return v2;
1169
+ }
1170
+ function getToken() {
1171
+ return loadConfig().token;
1172
+ }
1173
+ function setToken(token) {
1174
+ const config = loadConfig();
1175
+ config.token = token;
1176
+ saveConfig(config);
1177
+ }
1178
+ function clearToken() {
1179
+ const config = loadConfig();
1180
+ config.token = null;
1181
+ saveConfig(config);
1182
+ }
1183
+
1184
+ // src/lib/api-client.ts
1185
+ var ApiClient = class {
1186
+ baseUrl;
1187
+ token;
1188
+ constructor() {
1189
+ const config = loadConfig();
1190
+ this.baseUrl = config.api_url;
1191
+ this.token = config.token;
1192
+ }
1193
+ headers() {
1194
+ const h = {
1195
+ "Content-Type": "application/json"
1196
+ };
1197
+ if (this.token) {
1198
+ h["Authorization"] = `Bearer ${this.token}`;
1199
+ }
1200
+ return h;
1201
+ }
1202
+ async get(path) {
1203
+ const res = await fetch(`${this.baseUrl}${path}`, {
1204
+ headers: this.headers()
1205
+ });
1206
+ return res.json();
1207
+ }
1208
+ async post(path, body) {
1209
+ const res = await fetch(`${this.baseUrl}${path}`, {
1210
+ method: "POST",
1211
+ headers: this.headers(),
1212
+ body: body ? JSON.stringify(body) : void 0
1213
+ });
1214
+ return res.json();
1215
+ }
1216
+ async put(path, body) {
1217
+ const res = await fetch(`${this.baseUrl}${path}`, {
1218
+ method: "PUT",
1219
+ headers: this.headers(),
1220
+ body: JSON.stringify(body)
1221
+ });
1222
+ return res.json();
1223
+ }
1224
+ async delete(path) {
1225
+ const res = await fetch(`${this.baseUrl}${path}`, {
1226
+ method: "DELETE",
1227
+ headers: this.headers()
1228
+ });
1229
+ return res.json();
1230
+ }
1231
+ async getRaw(path) {
1232
+ return fetch(`${this.baseUrl}${path}`, {
1233
+ headers: this.headers()
1234
+ });
1235
+ }
1236
+ isAuthenticated() {
1237
+ return this.token !== null;
1238
+ }
1239
+ };
1240
+
1241
+ // src/commands/auth.ts
1242
+ var USER_CODE_CHARS = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
1243
+ function generateUserCode(length = 8) {
1244
+ const bytes = randomBytes(length);
1245
+ return Array.from(bytes).map((b) => USER_CODE_CHARS[b % USER_CODE_CHARS.length]).join("");
1246
+ }
1247
+ function openBrowser(url) {
1248
+ try {
1249
+ const platform = process.platform;
1250
+ if (platform === "darwin") {
1251
+ execSync(`open "${url}"`, { stdio: "ignore" });
1252
+ } else if (platform === "win32") {
1253
+ execSync(`start "" "${url}"`, { stdio: "ignore" });
1254
+ } else {
1255
+ execSync(`xdg-open "${url}"`, { stdio: "ignore" });
1256
+ }
1257
+ } catch {
1258
+ }
1259
+ }
1260
+ function sleep(ms) {
1261
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
1262
+ }
1263
+ var loginCommand = new Command("login").description("Log in to localskills.sh").option("--token <token>", "Use an API token directly (headless mode)").action(async (opts) => {
1264
+ if (opts.token) {
1265
+ setToken(opts.token);
1266
+ const client2 = new ApiClient();
1267
+ const res = await client2.get(
1268
+ "/api/cli/auth"
1269
+ );
1270
+ if (res.success && res.data) {
1271
+ const display = res.data.name || res.data.username || res.data.email;
1272
+ console.log(`Logged in as ${display}`);
1273
+ } else {
1274
+ console.error("Warning: Token stored but could not verify. Check that it's valid.");
1275
+ }
1276
+ return;
1277
+ }
1278
+ We("localskills login");
1279
+ const spinner = bt2();
1280
+ spinner.start("Initializing...");
1281
+ const deviceCode = randomBytes(32);
1282
+ const codeHash = createHash("sha256").update(deviceCode).digest("hex");
1283
+ const userCode = generateUserCode();
1284
+ const client = new ApiClient();
1285
+ const initRes = await client.post("/api/cli/auth/device", { codeHash, userCode });
1286
+ if (!initRes.success || !initRes.data) {
1287
+ spinner.stop(`Failed: ${initRes.error || "Could not start login"}`);
1288
+ process.exit(1);
1289
+ return;
1290
+ }
1291
+ spinner.stop("Ready!");
1292
+ const { verificationUrl } = initRes.data;
1293
+ R2.info(`Your verification code: ${userCode}`);
1294
+ R2.message(`Opening browser to ${verificationUrl}`);
1295
+ R2.message("If the browser doesn't open, visit the URL above manually.");
1296
+ openBrowser(verificationUrl);
1297
+ const pollSpinner = bt2();
1298
+ pollSpinner.start("Waiting for authorization...");
1299
+ const expiresAt = new Date(initRes.data.expiresAt).getTime();
1300
+ const POLL_INTERVAL = 2e3;
1301
+ while (Date.now() < expiresAt) {
1302
+ await sleep(POLL_INTERVAL);
1303
+ try {
1304
+ const pollRes = await client.get(
1305
+ `/api/cli/auth/poll?code_hash=${codeHash}`
1306
+ );
1307
+ if (!pollRes.success || !pollRes.data) continue;
1308
+ if (pollRes.data.status === "approved" && pollRes.data.token) {
1309
+ setToken(pollRes.data.token);
1310
+ pollSpinner.stop("Authorized!");
1311
+ const verifyClient = new ApiClient();
1312
+ const whoami = await verifyClient.get("/api/cli/auth");
1313
+ if (whoami.success && whoami.data) {
1314
+ const display = whoami.data.name || whoami.data.username || whoami.data.email;
1315
+ R2.success(`Logged in as ${display}`);
1316
+ }
1317
+ Le("Done!");
1318
+ return;
1319
+ }
1320
+ if (pollRes.data.status === "expired" || pollRes.data.status === "not_found") {
1321
+ pollSpinner.stop("Login expired. Please try again.");
1322
+ process.exit(1);
1323
+ return;
1324
+ }
1325
+ } catch {
1326
+ }
1327
+ }
1328
+ pollSpinner.stop("Login expired. Please try again.");
1329
+ process.exit(1);
1330
+ });
1331
+ var logoutCommand = new Command("logout").description("Log out of localskills.sh").action(() => {
1332
+ clearToken();
1333
+ console.log("Logged out.");
1334
+ });
1335
+ var whoamiCommand = new Command("whoami").description("Show current user info").action(async () => {
1336
+ const token = getToken();
1337
+ if (!token) {
1338
+ console.log("Not logged in. Run `localskills login` to authenticate.");
1339
+ process.exit(1);
1340
+ return;
1341
+ }
1342
+ const client = new ApiClient();
1343
+ const res = await client.get("/api/cli/auth");
1344
+ if (!res.success || !res.data) {
1345
+ console.error("Failed to fetch user info. Your token may be invalid.");
1346
+ console.error("Run `localskills login` to re-authenticate.");
1347
+ process.exit(1);
1348
+ return;
1349
+ }
1350
+ const { username, name, email } = res.data;
1351
+ if (name) console.log(` Name: ${name}`);
1352
+ if (username) console.log(` Username: @${username}`);
1353
+ console.log(` Email: ${email}`);
1354
+ });
1355
+
1356
+ // src/commands/install.ts
1357
+ import { Command as Command2 } from "commander";
1229
1358
 
1230
1359
  // src/lib/cache.ts
1231
1360
  import {
@@ -1275,6 +1404,11 @@ function toClaudeMD(content, skill) {
1275
1404
  function toPlainMD(content) {
1276
1405
  return content;
1277
1406
  }
1407
+ function stripFrontmatter(content) {
1408
+ const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
1409
+ if (match) return content.slice(match[0].length);
1410
+ return content;
1411
+ }
1278
1412
 
1279
1413
  // src/lib/symlink.ts
1280
1414
  import {
@@ -1424,8 +1558,8 @@ function uninstall2(installation, _slug) {
1424
1558
  }
1425
1559
  const parentDir = join3(installation.path, "..");
1426
1560
  try {
1427
- const { readdirSync: readdirSync2 } = __require("fs");
1428
- if (existsSync4(parentDir) && readdirSync2(parentDir).length === 0) {
1561
+ const { readdirSync: readdirSync3 } = __require("fs");
1562
+ if (existsSync4(parentDir) && readdirSync3(parentDir).length === 0) {
1429
1563
  rmSync3(parentDir, { recursive: true });
1430
1564
  }
1431
1565
  } catch {
@@ -1447,7 +1581,7 @@ var claudeAdapter = {
1447
1581
  // src/lib/installers/codex.ts
1448
1582
  import { join as join4 } from "path";
1449
1583
  import { homedir as homedir4 } from "os";
1450
- import { execSync } from "child_process";
1584
+ import { execSync as execSync2 } from "child_process";
1451
1585
 
1452
1586
  // src/lib/marked-sections.ts
1453
1587
  import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
@@ -1492,6 +1626,17 @@ function removeSection(filePath, slug) {
1492
1626
  writeFileSync4(filePath, result ? result + "\n" : "");
1493
1627
  return true;
1494
1628
  }
1629
+ function listSections(filePath) {
1630
+ if (!existsSync5(filePath)) return [];
1631
+ const content = readFileSync2(filePath, "utf-8");
1632
+ const regex = /<!-- localskills:start:(.+?) -->/g;
1633
+ const slugs = [];
1634
+ let match;
1635
+ while ((match = regex.exec(content)) !== null) {
1636
+ slugs.push(match[1]);
1637
+ }
1638
+ return slugs;
1639
+ }
1495
1640
 
1496
1641
  // src/lib/installers/codex.ts
1497
1642
  var descriptor3 = {
@@ -1505,7 +1650,7 @@ var descriptor3 = {
1505
1650
  function detect3() {
1506
1651
  let hasCommand = false;
1507
1652
  try {
1508
- execSync("which codex", { stdio: "ignore" });
1653
+ execSync2("which codex", { stdio: "ignore" });
1509
1654
  hasCommand = true;
1510
1655
  } catch {
1511
1656
  }
@@ -1722,7 +1867,7 @@ var copilotAdapter = {
1722
1867
  import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync6 } from "fs";
1723
1868
  import { join as join8 } from "path";
1724
1869
  import { homedir as homedir6 } from "os";
1725
- import { execSync as execSync2 } from "child_process";
1870
+ import { execSync as execSync3 } from "child_process";
1726
1871
  var descriptor7 = {
1727
1872
  id: "opencode",
1728
1873
  name: "OpenCode",
@@ -1735,7 +1880,7 @@ function detect7(projectDir) {
1735
1880
  const cwd = projectDir || process.cwd();
1736
1881
  let hasCommand = false;
1737
1882
  try {
1738
- execSync2("which opencode", { stdio: "ignore" });
1883
+ execSync3("which opencode", { stdio: "ignore" });
1739
1884
  hasCommand = true;
1740
1885
  } catch {
1741
1886
  }
@@ -1787,7 +1932,7 @@ var opencodeAdapter = {
1787
1932
  // src/lib/installers/aider.ts
1788
1933
  import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync7, readFileSync as readFileSync3 } from "fs";
1789
1934
  import { join as join9 } from "path";
1790
- import { execSync as execSync3 } from "child_process";
1935
+ import { execSync as execSync4 } from "child_process";
1791
1936
  var descriptor8 = {
1792
1937
  id: "aider",
1793
1938
  name: "Aider",
@@ -1799,7 +1944,7 @@ var descriptor8 = {
1799
1944
  function detect8() {
1800
1945
  let hasCommand = false;
1801
1946
  try {
1802
- execSync3("which aider", { stdio: "ignore" });
1947
+ execSync4("which aider", { stdio: "ignore" });
1803
1948
  hasCommand = true;
1804
1949
  } catch {
1805
1950
  }
@@ -2386,8 +2531,345 @@ ${transformed}`
2386
2531
  Le(`Pull complete. ${updated} updated, ${skipped} up to date.`);
2387
2532
  });
2388
2533
 
2534
+ // src/commands/publish.ts
2535
+ import { Command as Command6 } from "commander";
2536
+ import { readFileSync as readFileSync6, existsSync as existsSync14 } from "fs";
2537
+ import { resolve as resolve3, basename as basename2, extname as extname2 } from "path";
2538
+
2539
+ // src/lib/scanner.ts
2540
+ import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
2541
+ import { join as join12, basename, extname } from "path";
2542
+ import { homedir as homedir9 } from "os";
2543
+ import { readlinkSync as readlinkSync2, lstatSync as lstatSync2 } from "fs";
2544
+ function scanForSkills(projectDir) {
2545
+ const home = homedir9();
2546
+ const cwd = projectDir || process.cwd();
2547
+ const results = [];
2548
+ scanDirectory(join12(home, ".cursor", "rules"), ".mdc", "cursor", "global", results);
2549
+ scanDirectory(join12(cwd, ".cursor", "rules"), ".mdc", "cursor", "project", results);
2550
+ scanClaudeSkills(join12(home, ".claude", "skills"), "global", results);
2551
+ scanClaudeSkills(join12(cwd, ".claude", "skills"), "project", results);
2552
+ scanSingleFile(join12(home, ".codex", "AGENTS.md"), "codex", "global", results);
2553
+ scanSingleFile(join12(cwd, "AGENTS.md"), "codex", "project", results);
2554
+ scanSingleFile(
2555
+ join12(home, ".codeium", "windsurf", "memories", "global_rules.md"),
2556
+ "windsurf",
2557
+ "global",
2558
+ results
2559
+ );
2560
+ scanDirectory(join12(cwd, ".windsurf", "rules"), ".md", "windsurf", "project", results);
2561
+ scanDirectory(join12(cwd, ".clinerules"), ".md", "cline", "project", results);
2562
+ scanSingleFile(
2563
+ join12(cwd, ".github", "copilot-instructions.md"),
2564
+ "copilot",
2565
+ "project",
2566
+ results
2567
+ );
2568
+ scanDirectory(join12(home, ".config", "opencode", "rules"), ".md", "opencode", "global", results);
2569
+ scanDirectory(join12(cwd, ".opencode", "rules"), ".md", "opencode", "project", results);
2570
+ scanDirectory(join12(cwd, ".aider", "skills"), ".md", "aider", "project", results);
2571
+ return results;
2572
+ }
2573
+ function filterTracked(detected, config) {
2574
+ const trackedPaths = /* @__PURE__ */ new Set();
2575
+ for (const skill of Object.values(config.installed_skills)) {
2576
+ for (const inst of skill.installations) {
2577
+ trackedPaths.add(inst.path);
2578
+ }
2579
+ }
2580
+ const cacheDir = join12(homedir9(), ".localskills", "cache");
2581
+ return detected.filter((skill) => {
2582
+ if (trackedPaths.has(skill.filePath)) return false;
2583
+ try {
2584
+ const stat = lstatSync2(skill.filePath);
2585
+ if (stat.isSymbolicLink()) {
2586
+ const target = readlinkSync2(skill.filePath);
2587
+ if (target.startsWith(cacheDir)) return false;
2588
+ }
2589
+ } catch {
2590
+ }
2591
+ return true;
2592
+ });
2593
+ }
2594
+ function slugFromFilename(filename) {
2595
+ return basename(filename, extname(filename));
2596
+ }
2597
+ function nameFromSlug(slug) {
2598
+ return slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2599
+ }
2600
+ function scanDirectory(dir, ext, platform, scope, results) {
2601
+ if (!existsSync13(dir)) return;
2602
+ let entries;
2603
+ try {
2604
+ entries = readdirSync2(dir);
2605
+ } catch {
2606
+ return;
2607
+ }
2608
+ for (const entry of entries) {
2609
+ if (!entry.endsWith(ext)) continue;
2610
+ const filePath = join12(dir, entry);
2611
+ try {
2612
+ const raw = readFileSync5(filePath, "utf-8");
2613
+ const content = stripFrontmatter(raw).trim();
2614
+ if (!content) continue;
2615
+ const slug = slugFromFilename(entry);
2616
+ results.push({
2617
+ filePath,
2618
+ platform,
2619
+ scope,
2620
+ suggestedName: nameFromSlug(slug),
2621
+ suggestedSlug: slug,
2622
+ content
2623
+ });
2624
+ } catch {
2625
+ }
2626
+ }
2627
+ }
2628
+ function scanClaudeSkills(skillsDir, scope, results) {
2629
+ if (!existsSync13(skillsDir)) return;
2630
+ let entries;
2631
+ try {
2632
+ entries = readdirSync2(skillsDir);
2633
+ } catch {
2634
+ return;
2635
+ }
2636
+ for (const entry of entries) {
2637
+ const skillFile = join12(skillsDir, entry, "SKILL.md");
2638
+ if (!existsSync13(skillFile)) continue;
2639
+ try {
2640
+ const raw = readFileSync5(skillFile, "utf-8");
2641
+ const content = stripFrontmatter(raw).trim();
2642
+ if (!content) continue;
2643
+ results.push({
2644
+ filePath: skillFile,
2645
+ platform: "claude",
2646
+ scope,
2647
+ suggestedName: nameFromSlug(entry),
2648
+ suggestedSlug: entry,
2649
+ content
2650
+ });
2651
+ } catch {
2652
+ }
2653
+ }
2654
+ }
2655
+ function scanSingleFile(filePath, platform, scope, results) {
2656
+ if (!existsSync13(filePath)) return;
2657
+ let raw;
2658
+ try {
2659
+ raw = readFileSync5(filePath, "utf-8");
2660
+ } catch {
2661
+ return;
2662
+ }
2663
+ const sections = listSections(filePath);
2664
+ if (sections.length > 0) {
2665
+ for (const slug2 of sections) {
2666
+ const startMarker = `<!-- localskills:start:${slug2} -->`;
2667
+ const endMarker = `<!-- localskills:end:${slug2} -->`;
2668
+ const startIdx = raw.indexOf(startMarker);
2669
+ const endIdx = raw.indexOf(endMarker);
2670
+ if (startIdx === -1 || endIdx === -1) continue;
2671
+ const content2 = raw.slice(startIdx + startMarker.length, endIdx).trim();
2672
+ if (!content2) continue;
2673
+ results.push({
2674
+ filePath,
2675
+ platform,
2676
+ scope,
2677
+ suggestedName: nameFromSlug(slug2),
2678
+ suggestedSlug: slug2,
2679
+ content: content2
2680
+ });
2681
+ }
2682
+ return;
2683
+ }
2684
+ const content = stripFrontmatter(raw).trim();
2685
+ if (!content) return;
2686
+ const slug = slugFromFilename(filePath);
2687
+ results.push({
2688
+ filePath,
2689
+ platform,
2690
+ scope,
2691
+ suggestedName: nameFromSlug(slug),
2692
+ suggestedSlug: slug,
2693
+ content
2694
+ });
2695
+ }
2696
+
2697
+ // src/commands/publish.ts
2698
+ 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(
2699
+ "--visibility <visibility>",
2700
+ "Visibility: public, private, or unlisted",
2701
+ "private"
2702
+ ).option("-m, --message <message>", "Version message").action(
2703
+ async (fileArg, opts) => {
2704
+ const client = new ApiClient();
2705
+ if (!client.isAuthenticated()) {
2706
+ console.error("Not authenticated. Run `localskills login` first.");
2707
+ process.exit(1);
2708
+ }
2709
+ const teamsRes = await client.get("/api/tenants");
2710
+ if (!teamsRes.success || !teamsRes.data || teamsRes.data.length === 0) {
2711
+ console.error(
2712
+ "No teams found. Create a team at localskills.sh first."
2713
+ );
2714
+ process.exit(1);
2715
+ return;
2716
+ }
2717
+ const teams = teamsRes.data;
2718
+ if (fileArg) {
2719
+ const filePath = resolve3(fileArg);
2720
+ if (!existsSync14(filePath)) {
2721
+ console.error(`File not found: ${filePath}`);
2722
+ process.exit(1);
2723
+ return;
2724
+ }
2725
+ const raw = readFileSync6(filePath, "utf-8");
2726
+ const content = stripFrontmatter(raw).trim();
2727
+ if (!content) {
2728
+ console.error("File is empty after stripping frontmatter.");
2729
+ process.exit(1);
2730
+ return;
2731
+ }
2732
+ const defaultSlug = basename2(filePath, extname2(filePath));
2733
+ const defaultName = defaultSlug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2734
+ const skillName = opts.name || defaultName;
2735
+ const visibility = validateVisibility(opts.visibility || "private");
2736
+ const tenantId = await resolveTeam(teams, opts.team);
2737
+ await uploadSkill(client, {
2738
+ name: skillName,
2739
+ content,
2740
+ tenantId,
2741
+ visibility
2742
+ });
2743
+ } else {
2744
+ We("localskills publish");
2745
+ const spinner = bt2();
2746
+ spinner.start("Scanning for skills...");
2747
+ const config = loadConfig();
2748
+ const allDetected = scanForSkills();
2749
+ const detected = filterTracked(allDetected, config);
2750
+ spinner.stop(
2751
+ detected.length > 0 ? `Found ${detected.length} skill file${detected.length !== 1 ? "s" : ""}.` : "No unpublished skill files found."
2752
+ );
2753
+ if (detected.length === 0) {
2754
+ Le("Nothing to publish.");
2755
+ return;
2756
+ }
2757
+ const selected = await je({
2758
+ message: "Select skills to publish",
2759
+ options: detected.map((s) => ({
2760
+ value: s,
2761
+ label: s.suggestedName,
2762
+ hint: `${s.platform}/${s.scope} ${shortenPath(s.filePath)}`
2763
+ })),
2764
+ required: true
2765
+ });
2766
+ if (Ct(selected)) {
2767
+ Ne("Cancelled.");
2768
+ process.exit(0);
2769
+ }
2770
+ const skills = selected;
2771
+ const tenantId = await resolveTeam(teams, opts.team);
2772
+ for (const skill of skills) {
2773
+ R2.step(`Publishing ${skill.suggestedName}...`);
2774
+ const name = await Ze({
2775
+ message: "Skill name?",
2776
+ initialValue: skill.suggestedName,
2777
+ validate: (v) => {
2778
+ if (!v || v.length < 1) return "Name is required";
2779
+ if (v.length > 100) return "Name must be 100 characters or less";
2780
+ }
2781
+ });
2782
+ if (Ct(name)) {
2783
+ Ne("Cancelled.");
2784
+ process.exit(0);
2785
+ }
2786
+ const visibility = await Je({
2787
+ message: "Visibility?",
2788
+ options: [
2789
+ { value: "private", label: "Private", hint: "Only team members" },
2790
+ { value: "public", label: "Public", hint: "Anyone can install" },
2791
+ { value: "unlisted", label: "Unlisted", hint: "Accessible via direct link" }
2792
+ ],
2793
+ initialValue: "private"
2794
+ });
2795
+ if (Ct(visibility)) {
2796
+ Ne("Cancelled.");
2797
+ process.exit(0);
2798
+ }
2799
+ await uploadSkill(client, {
2800
+ name,
2801
+ content: skill.content,
2802
+ tenantId,
2803
+ visibility
2804
+ });
2805
+ }
2806
+ Le("Done!");
2807
+ }
2808
+ }
2809
+ );
2810
+ async function resolveTeam(teams, teamFlag) {
2811
+ if (teamFlag) {
2812
+ const match = teams.find((t) => t.id === teamFlag || t.slug === teamFlag);
2813
+ if (!match) {
2814
+ console.error(`Team not found: ${teamFlag}`);
2815
+ process.exit(1);
2816
+ }
2817
+ return match.id;
2818
+ }
2819
+ if (teams.length === 1) {
2820
+ return teams[0].id;
2821
+ }
2822
+ const selected = await Je({
2823
+ message: "Which team?",
2824
+ options: teams.map((t) => ({
2825
+ value: t.id,
2826
+ label: t.name,
2827
+ hint: t.slug
2828
+ }))
2829
+ });
2830
+ if (Ct(selected)) {
2831
+ Ne("Cancelled.");
2832
+ process.exit(0);
2833
+ }
2834
+ return selected;
2835
+ }
2836
+ async function uploadSkill(client, params) {
2837
+ const spinner = bt2();
2838
+ spinner.start(`Uploading ${params.name}...`);
2839
+ const res = await client.post("/api/skills", {
2840
+ name: params.name,
2841
+ content: params.content,
2842
+ tenantId: params.tenantId,
2843
+ visibility: params.visibility
2844
+ });
2845
+ if (!res.success || !res.data) {
2846
+ spinner.stop(`Failed: ${res.error || "Unknown error"}`);
2847
+ return;
2848
+ }
2849
+ spinner.stop(`Published!`);
2850
+ R2.success(`\u2192 localskills.sh/s/${res.data.slug}`);
2851
+ }
2852
+ function validateVisibility(value) {
2853
+ if (value === "public" || value === "private" || value === "unlisted") {
2854
+ return value;
2855
+ }
2856
+ console.error(`Invalid visibility: ${value}. Use public, private, or unlisted.`);
2857
+ process.exit(1);
2858
+ }
2859
+ function shortenPath(filePath) {
2860
+ const home = __require("os").homedir();
2861
+ if (filePath.startsWith(home)) {
2862
+ return "~" + filePath.slice(home.length);
2863
+ }
2864
+ const cwd = process.cwd();
2865
+ if (filePath.startsWith(cwd)) {
2866
+ return "." + filePath.slice(cwd.length);
2867
+ }
2868
+ return filePath;
2869
+ }
2870
+
2389
2871
  // src/index.ts
2390
- var program = new Command6();
2872
+ var program = new Command7();
2391
2873
  program.name("localskills").description("Install and manage agent skills from localskills.sh").version("0.1.0");
2392
2874
  program.addCommand(loginCommand);
2393
2875
  program.addCommand(logoutCommand);
@@ -2396,4 +2878,5 @@ program.addCommand(installCommand);
2396
2878
  program.addCommand(uninstallCommand);
2397
2879
  program.addCommand(listCommand);
2398
2880
  program.addCommand(pullCommand);
2881
+ program.addCommand(publishCommand);
2399
2882
  program.parse();