@localskills/cli 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +310 -17
  2. package/package.json +3 -3
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 Command9 } from "commander";
164
+ import { Command as Command10 } from "commander";
165
165
 
166
166
  // src/commands/auth.ts
167
167
  import { Command } from "commander";
@@ -521,6 +521,23 @@ var x = class {
521
521
  }
522
522
  }
523
523
  };
524
+ var kt = class extends x {
525
+ get cursor() {
526
+ return this.value ? 0 : 1;
527
+ }
528
+ get _value() {
529
+ return this.cursor === 0;
530
+ }
531
+ constructor(e2) {
532
+ super(e2, false), this.value = !!e2.initialValue, this.on("userInput", () => {
533
+ this.value = this._value;
534
+ }), this.on("confirm", (s) => {
535
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s, this.state = "submit", this.close();
536
+ }), this.on("cursor", () => {
537
+ this.value = !this.value;
538
+ });
539
+ }
540
+ };
524
541
  var Lt = class extends x {
525
542
  options;
526
543
  cursor = 0;
@@ -876,6 +893,33 @@ var X2 = (t) => {
876
893
  for (const A of f) for (const w of A) B2.push(w);
877
894
  return h && B2.push(g), B2;
878
895
  };
896
+ var Re = (t) => {
897
+ const r = t.active ?? "Yes", s = t.inactive ?? "No";
898
+ return new kt({ active: r, inactive: s, signal: t.signal, input: t.input, output: t.output, initialValue: t.initialValue ?? true, render() {
899
+ const i = t.withGuide ?? _.withGuide, a = `${i ? `${import_picocolors2.default.gray(d)}
900
+ ` : ""}${W2(this.state)} ${t.message}
901
+ `, o = this.value ? r : s;
902
+ switch (this.state) {
903
+ case "submit": {
904
+ const u = i ? `${import_picocolors2.default.gray(d)} ` : "";
905
+ return `${a}${u}${import_picocolors2.default.dim(o)}`;
906
+ }
907
+ case "cancel": {
908
+ const u = i ? `${import_picocolors2.default.gray(d)} ` : "";
909
+ return `${a}${u}${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(o))}${i ? `
910
+ ${import_picocolors2.default.gray(d)}` : ""}`;
911
+ }
912
+ default: {
913
+ const u = i ? `${import_picocolors2.default.cyan(d)} ` : "", l = i ? import_picocolors2.default.cyan(x2) : "";
914
+ return `${a}${u}${this.value ? `${import_picocolors2.default.green(Q2)} ${r}` : `${import_picocolors2.default.dim(H2)} ${import_picocolors2.default.dim(r)}`}${t.vertical ? i ? `
915
+ ${import_picocolors2.default.cyan(d)} ` : `
916
+ ` : ` ${import_picocolors2.default.dim("/")} `}${this.value ? `${import_picocolors2.default.dim(H2)} ${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.green(Q2)} ${s}`}
917
+ ${l}
918
+ `;
919
+ }
920
+ }
921
+ } }).prompt();
922
+ };
879
923
  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 } = {}) => {
880
924
  const u = [], l = o ?? _.withGuide, n = l ? s : "", c = l ? `${r} ` : "", g = l ? `${s} ` : "";
881
925
  for (let p = 0; p < a; p++) u.push(n);
@@ -1101,14 +1145,13 @@ ${l}
1101
1145
  } }).prompt();
1102
1146
 
1103
1147
  // src/lib/config.ts
1104
- import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "fs";
1148
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, copyFileSync } from "fs";
1105
1149
  import { join } from "path";
1106
1150
  import { homedir } from "os";
1107
1151
  var CONFIG_DIR = join(homedir(), ".localskills");
1108
1152
  var CONFIG_PATH = join(CONFIG_DIR, "config.json");
1109
- var DEFAULT_CONFIG = {
1110
- config_version: 2,
1111
- api_url: "https://localskills.sh",
1153
+ var DEFAULT_PROFILE_NAME = "default";
1154
+ var DEFAULT_PROFILE = {
1112
1155
  token: null,
1113
1156
  installed_skills: {},
1114
1157
  defaults: {
@@ -1117,21 +1160,109 @@ var DEFAULT_CONFIG = {
1117
1160
  },
1118
1161
  anonymous_key: null
1119
1162
  };
1120
- function loadConfig() {
1163
+ var DEFAULT_API_URL = "https://localskills.sh";
1164
+ var ProfileNotFoundError = class extends Error {
1165
+ constructor(profileName, availableProfiles, source) {
1166
+ const sourceLabel = source === "flag" ? "" : source === "env" ? " (from LOCALSKILLS_PROFILE)" : "";
1167
+ super(`Profile "${profileName}"${sourceLabel} does not exist. Available profiles: ${availableProfiles.join(", ")}`);
1168
+ this.profileName = profileName;
1169
+ this.availableProfiles = availableProfiles;
1170
+ this.source = source;
1171
+ this.name = "ProfileNotFoundError";
1172
+ }
1173
+ };
1174
+ var _profileOverride;
1175
+ function setProfileOverride(name) {
1176
+ _profileOverride = name;
1177
+ }
1178
+ function resolveProfileName(config) {
1179
+ if (_profileOverride) {
1180
+ if (!config.profiles[_profileOverride]) {
1181
+ throw new ProfileNotFoundError(
1182
+ _profileOverride,
1183
+ Object.keys(config.profiles),
1184
+ "flag"
1185
+ );
1186
+ }
1187
+ return _profileOverride;
1188
+ }
1189
+ const envProfile = process.env.LOCALSKILLS_PROFILE;
1190
+ if (envProfile) {
1191
+ if (!config.profiles[envProfile]) {
1192
+ throw new ProfileNotFoundError(
1193
+ envProfile,
1194
+ Object.keys(config.profiles),
1195
+ "env"
1196
+ );
1197
+ }
1198
+ return envProfile;
1199
+ }
1200
+ return config.active_profile;
1201
+ }
1202
+ function getActiveProfileName() {
1203
+ const full = loadFullConfig();
1204
+ return resolveProfileName(full);
1205
+ }
1206
+ function validateV3(config) {
1207
+ if (!config.profiles || typeof config.profiles !== "object") {
1208
+ config.profiles = {
1209
+ [DEFAULT_PROFILE_NAME]: { ...DEFAULT_PROFILE, installed_skills: {} }
1210
+ };
1211
+ }
1212
+ if (!config.profiles[DEFAULT_PROFILE_NAME]) {
1213
+ config.profiles[DEFAULT_PROFILE_NAME] = { ...DEFAULT_PROFILE, installed_skills: {} };
1214
+ }
1215
+ if (!config.active_profile || !config.profiles[config.active_profile]) {
1216
+ config.active_profile = DEFAULT_PROFILE_NAME;
1217
+ }
1218
+ return config;
1219
+ }
1220
+ function loadFullConfig() {
1121
1221
  if (!existsSync(CONFIG_PATH)) {
1122
- return { ...DEFAULT_CONFIG, installed_skills: {} };
1222
+ return {
1223
+ config_version: 3,
1224
+ api_url: DEFAULT_API_URL,
1225
+ active_profile: DEFAULT_PROFILE_NAME,
1226
+ profiles: {
1227
+ [DEFAULT_PROFILE_NAME]: { ...DEFAULT_PROFILE, installed_skills: {} }
1228
+ }
1229
+ };
1123
1230
  }
1124
1231
  try {
1125
1232
  const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1233
+ if (raw.config_version === 3) {
1234
+ return validateV3(raw);
1235
+ }
1236
+ let v2;
1126
1237
  if (!raw.config_version || raw.config_version < 2) {
1127
- return migrateV1toV2(raw);
1238
+ v2 = migrateV1toV2(raw);
1239
+ } else {
1240
+ v2 = raw;
1241
+ }
1242
+ const v3 = migrateV2toV3(v2);
1243
+ saveFullConfig(v3);
1244
+ return v3;
1245
+ } catch (err) {
1246
+ if (err instanceof SyntaxError) {
1247
+ console.error(`Warning: Config file is corrupt (${CONFIG_PATH}). Backing up and starting fresh.`);
1248
+ try {
1249
+ copyFileSync(CONFIG_PATH, CONFIG_PATH + ".bak");
1250
+ console.error(` Backup saved to ${CONFIG_PATH}.bak`);
1251
+ } catch {
1252
+ }
1253
+ return {
1254
+ config_version: 3,
1255
+ api_url: DEFAULT_API_URL,
1256
+ active_profile: DEFAULT_PROFILE_NAME,
1257
+ profiles: {
1258
+ [DEFAULT_PROFILE_NAME]: { ...DEFAULT_PROFILE, installed_skills: {} }
1259
+ }
1260
+ };
1128
1261
  }
1129
- return { ...DEFAULT_CONFIG, ...raw };
1130
- } catch {
1131
- return { ...DEFAULT_CONFIG, installed_skills: {} };
1262
+ throw err;
1132
1263
  }
1133
1264
  }
1134
- function saveConfig(config) {
1265
+ function saveFullConfig(config) {
1135
1266
  mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
1136
1267
  writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", {
1137
1268
  mode: 384
@@ -1142,10 +1273,35 @@ function saveConfig(config) {
1142
1273
  } catch {
1143
1274
  }
1144
1275
  }
1276
+ function loadConfig() {
1277
+ const full = loadFullConfig();
1278
+ const profileName = resolveProfileName(full);
1279
+ const profile = full.profiles[profileName] ?? { ...DEFAULT_PROFILE, installed_skills: {} };
1280
+ return {
1281
+ config_version: 2,
1282
+ api_url: full.api_url,
1283
+ token: profile.token,
1284
+ installed_skills: profile.installed_skills,
1285
+ defaults: profile.defaults,
1286
+ anonymous_key: profile.anonymous_key ?? null
1287
+ };
1288
+ }
1289
+ function saveConfig(config) {
1290
+ const full = loadFullConfig();
1291
+ const profileName = resolveProfileName(full);
1292
+ full.api_url = config.api_url;
1293
+ full.profiles[profileName] = {
1294
+ token: config.token,
1295
+ installed_skills: config.installed_skills,
1296
+ defaults: config.defaults,
1297
+ anonymous_key: config.anonymous_key ?? null
1298
+ };
1299
+ saveFullConfig(full);
1300
+ }
1145
1301
  function migrateV1toV2(v1) {
1146
1302
  const v2 = {
1147
1303
  config_version: 2,
1148
- api_url: v1.api_url || DEFAULT_CONFIG.api_url,
1304
+ api_url: v1.api_url || DEFAULT_API_URL,
1149
1305
  token: v1.token,
1150
1306
  installed_skills: {},
1151
1307
  defaults: {
@@ -1174,9 +1330,23 @@ function migrateV1toV2(v1) {
1174
1330
  ]
1175
1331
  };
1176
1332
  }
1177
- saveConfig(v2);
1178
1333
  return v2;
1179
1334
  }
1335
+ function migrateV2toV3(v2) {
1336
+ return {
1337
+ config_version: 3,
1338
+ api_url: v2.api_url || DEFAULT_API_URL,
1339
+ active_profile: DEFAULT_PROFILE_NAME,
1340
+ profiles: {
1341
+ [DEFAULT_PROFILE_NAME]: {
1342
+ token: v2.token,
1343
+ installed_skills: v2.installed_skills,
1344
+ defaults: v2.defaults,
1345
+ anonymous_key: v2.anonymous_key ?? null
1346
+ }
1347
+ }
1348
+ };
1349
+ }
1180
1350
  function getToken() {
1181
1351
  return loadConfig().token;
1182
1352
  }
@@ -3283,9 +3453,124 @@ async function uploadAnonymousSkill(client, params) {
3283
3453
  R2.info(`Install: localskills install ${res.data.publicId}`);
3284
3454
  }
3285
3455
 
3456
+ // src/commands/profile.ts
3457
+ import { Command as Command9 } from "commander";
3458
+ var PROFILE_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
3459
+ var MAX_PROFILE_NAME_LENGTH = 32;
3460
+ var profileCommand = new Command9("profile").description("Manage CLI profiles for multiple accounts");
3461
+ profileCommand.command("list").description("List all profiles").action(() => {
3462
+ const config = loadFullConfig();
3463
+ const persisted = config.active_profile;
3464
+ let resolved;
3465
+ try {
3466
+ resolved = getActiveProfileName();
3467
+ } catch (err) {
3468
+ if (err instanceof ProfileNotFoundError) {
3469
+ console.error(`Warning: ${err.message}
3470
+ `);
3471
+ resolved = persisted;
3472
+ } else {
3473
+ throw err;
3474
+ }
3475
+ }
3476
+ const names = Object.keys(config.profiles);
3477
+ console.log("Profiles:\n");
3478
+ for (const name of names) {
3479
+ const profile = config.profiles[name];
3480
+ let marker = "";
3481
+ if (name === resolved && name === persisted) {
3482
+ marker = " (active)";
3483
+ } else if (name === resolved) {
3484
+ marker = " (active via override)";
3485
+ } else if (name === persisted) {
3486
+ marker = " (default)";
3487
+ }
3488
+ const auth = profile.token ? "authenticated" : "not authenticated";
3489
+ const skillCount = Object.keys(profile.installed_skills).length;
3490
+ console.log(` ${name}${marker} \u2014 ${auth}, ${skillCount} skill${skillCount !== 1 ? "s" : ""}`);
3491
+ }
3492
+ });
3493
+ profileCommand.command("create <name>").description("Create a new profile").action((name) => {
3494
+ if (!PROFILE_NAME_RE.test(name)) {
3495
+ console.error(
3496
+ `Error: Invalid profile name "${name}". Profile names must use lowercase letters, numbers, and hyphens, and must start with a letter or number.`
3497
+ );
3498
+ process.exit(1);
3499
+ }
3500
+ if (name.length > MAX_PROFILE_NAME_LENGTH) {
3501
+ console.error(`Error: Profile name must be ${MAX_PROFILE_NAME_LENGTH} characters or fewer.`);
3502
+ process.exit(1);
3503
+ }
3504
+ const config = loadFullConfig();
3505
+ if (config.profiles[name]) {
3506
+ console.error(`Error: Profile "${name}" already exists.`);
3507
+ process.exit(1);
3508
+ }
3509
+ config.profiles[name] = {
3510
+ token: null,
3511
+ installed_skills: {},
3512
+ defaults: {
3513
+ scope: "project",
3514
+ method: "symlink"
3515
+ },
3516
+ anonymous_key: null
3517
+ };
3518
+ saveFullConfig(config);
3519
+ console.log(`Profile "${name}" created.`);
3520
+ });
3521
+ profileCommand.command("switch <name>").description("Switch the active profile").action((name) => {
3522
+ const config = loadFullConfig();
3523
+ if (!config.profiles[name]) {
3524
+ console.error(`Error: Profile "${name}" does not exist.`);
3525
+ console.error(`Available profiles: ${Object.keys(config.profiles).join(", ")}`);
3526
+ process.exit(1);
3527
+ }
3528
+ config.active_profile = name;
3529
+ saveFullConfig(config);
3530
+ console.log(`Switched to profile "${name}".`);
3531
+ });
3532
+ profileCommand.command("delete <name>").description("Delete a profile").option("-f, --force", "Skip confirmation prompt").action(async (name, opts) => {
3533
+ if (name === DEFAULT_PROFILE_NAME) {
3534
+ console.error(`Error: Cannot delete the "${DEFAULT_PROFILE_NAME}" profile.`);
3535
+ process.exit(1);
3536
+ }
3537
+ const config = loadFullConfig();
3538
+ if (!config.profiles[name]) {
3539
+ console.error(`Error: Profile "${name}" does not exist.`);
3540
+ process.exit(1);
3541
+ }
3542
+ if (name === config.active_profile) {
3543
+ console.error(`Error: Cannot delete the active profile "${name}". Run \`localskills profile switch <other>\` first.`);
3544
+ process.exit(1);
3545
+ }
3546
+ if (!opts.force) {
3547
+ const profile = config.profiles[name];
3548
+ const skillCount = Object.keys(profile.installed_skills).length;
3549
+ const details = [
3550
+ profile.token ? "authenticated session" : null,
3551
+ skillCount > 0 ? `${skillCount} installed skill${skillCount !== 1 ? "s" : ""}` : null
3552
+ ].filter(Boolean);
3553
+ const detailStr = details.length > 0 ? ` (has ${details.join(", ")})` : "";
3554
+ const confirmed = await Re({
3555
+ message: `Delete profile "${name}"${detailStr}? This cannot be undone.`
3556
+ });
3557
+ if (Ct(confirmed) || !confirmed) {
3558
+ console.log("Cancelled.");
3559
+ return;
3560
+ }
3561
+ }
3562
+ delete config.profiles[name];
3563
+ saveFullConfig(config);
3564
+ console.log(`Profile "${name}" deleted.`);
3565
+ });
3566
+
3286
3567
  // src/index.ts
3287
- var program = new Command9();
3288
- program.name("localskills").description("Install and manage agent skills from localskills.sh").version("0.1.0");
3568
+ var program = new Command10();
3569
+ program.name("localskills").description("Install and manage agent skills from localskills.sh").version("0.1.0").option("--profile <name>", "Use a specific profile");
3570
+ program.hook("preAction", (thisCommand) => {
3571
+ const opts = thisCommand.opts();
3572
+ setProfileOverride(opts.profile);
3573
+ });
3289
3574
  program.addCommand(loginCommand);
3290
3575
  program.addCommand(logoutCommand);
3291
3576
  program.addCommand(whoamiCommand);
@@ -3296,4 +3581,12 @@ program.addCommand(pullCommand);
3296
3581
  program.addCommand(publishCommand);
3297
3582
  program.addCommand(pushCommand);
3298
3583
  program.addCommand(shareCommand);
3299
- program.parse();
3584
+ program.addCommand(profileCommand);
3585
+ program.parseAsync().catch((err) => {
3586
+ if (err instanceof ProfileNotFoundError) {
3587
+ console.error(`Error: ${err.message}`);
3588
+ } else {
3589
+ console.error(err instanceof Error ? err.message : String(err));
3590
+ }
3591
+ process.exit(1);
3592
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localskills/cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "CLI for localskills.sh — install agent skills locally",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,8 +19,8 @@
19
19
  "tsup": "^8",
20
20
  "typescript": "^5.7",
21
21
  "vitest": "^3",
22
- "@localskills/tsconfig": "0.0.0",
23
- "@localskills/shared": "0.0.0"
22
+ "@localskills/shared": "0.0.0",
23
+ "@localskills/tsconfig": "0.0.0"
24
24
  },
25
25
  "scripts": {
26
26
  "build": "tsup",