@localskills/cli 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +264 -188
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -165,184 +165,8 @@ 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((resolve4) => {
318
- rl.question("Paste your API token: ", (answer) => {
319
- rl.close();
320
- resolve4(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
348
172
  var import_picocolors = __toESM(require_picocolors(), 1);
@@ -1276,6 +1100,262 @@ ${l}
1276
1100
  }
1277
1101
  } }).prompt();
1278
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";
1358
+
1279
1359
  // src/lib/cache.ts
1280
1360
  import {
1281
1361
  existsSync as existsSync11,
@@ -1501,7 +1581,7 @@ var claudeAdapter = {
1501
1581
  // src/lib/installers/codex.ts
1502
1582
  import { join as join4 } from "path";
1503
1583
  import { homedir as homedir4 } from "os";
1504
- import { execSync } from "child_process";
1584
+ import { execSync as execSync2 } from "child_process";
1505
1585
 
1506
1586
  // src/lib/marked-sections.ts
1507
1587
  import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
@@ -1570,7 +1650,7 @@ var descriptor3 = {
1570
1650
  function detect3() {
1571
1651
  let hasCommand = false;
1572
1652
  try {
1573
- execSync("which codex", { stdio: "ignore" });
1653
+ execSync2("which codex", { stdio: "ignore" });
1574
1654
  hasCommand = true;
1575
1655
  } catch {
1576
1656
  }
@@ -1787,7 +1867,7 @@ var copilotAdapter = {
1787
1867
  import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, unlinkSync as unlinkSync6 } from "fs";
1788
1868
  import { join as join8 } from "path";
1789
1869
  import { homedir as homedir6 } from "os";
1790
- import { execSync as execSync2 } from "child_process";
1870
+ import { execSync as execSync3 } from "child_process";
1791
1871
  var descriptor7 = {
1792
1872
  id: "opencode",
1793
1873
  name: "OpenCode",
@@ -1800,7 +1880,7 @@ function detect7(projectDir) {
1800
1880
  const cwd = projectDir || process.cwd();
1801
1881
  let hasCommand = false;
1802
1882
  try {
1803
- execSync2("which opencode", { stdio: "ignore" });
1883
+ execSync3("which opencode", { stdio: "ignore" });
1804
1884
  hasCommand = true;
1805
1885
  } catch {
1806
1886
  }
@@ -1852,7 +1932,7 @@ var opencodeAdapter = {
1852
1932
  // src/lib/installers/aider.ts
1853
1933
  import { existsSync as existsSync10, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, unlinkSync as unlinkSync7, readFileSync as readFileSync3 } from "fs";
1854
1934
  import { join as join9 } from "path";
1855
- import { execSync as execSync3 } from "child_process";
1935
+ import { execSync as execSync4 } from "child_process";
1856
1936
  var descriptor8 = {
1857
1937
  id: "aider",
1858
1938
  name: "Aider",
@@ -1864,7 +1944,7 @@ var descriptor8 = {
1864
1944
  function detect8() {
1865
1945
  let hasCommand = false;
1866
1946
  try {
1867
- execSync3("which aider", { stdio: "ignore" });
1947
+ execSync4("which aider", { stdio: "ignore" });
1868
1948
  hasCommand = true;
1869
1949
  } catch {
1870
1950
  }
@@ -2208,10 +2288,6 @@ var installCommand = new Command2("install").description("Install a skill locall
2208
2288
  const explicitScope = opts.global ? "global" : opts.project !== void 0 ? "project" : null;
2209
2289
  const explicitMethod = opts.copy ? "copy" : opts.symlink ? "symlink" : null;
2210
2290
  if (!slugArg) {
2211
- if (!client.isAuthenticated()) {
2212
- console.error("Not authenticated. Run `localskills login` first.");
2213
- process.exit(1);
2214
- }
2215
2291
  const spinner2 = bt2();
2216
2292
  spinner2.start("Fetching available skills...");
2217
2293
  const res2 = await client.get("/api/skills");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localskills/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "CLI for localskills.sh — install agent skills locally",
5
5
  "type": "module",
6
6
  "bin": {