@skillport/cli 0.1.4 → 0.1.7

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 +462 -57
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -2502,8 +2502,40 @@ async function uninstallCommand(skillId) {
2502
2502
  init_config();
2503
2503
  import { createServer } from "node:http";
2504
2504
  import { randomBytes } from "node:crypto";
2505
- import chalk9 from "chalk";
2505
+ import chalk10 from "chalk";
2506
2506
  import inquirer4 from "inquirer";
2507
+
2508
+ // src/utils/register-key.ts
2509
+ init_config();
2510
+ import chalk9 from "chalk";
2511
+ async function registerPublicKey(config) {
2512
+ if (!hasKeys()) {
2513
+ return false;
2514
+ }
2515
+ try {
2516
+ const publicKey = loadPublicKey2();
2517
+ const res = await fetch(`${config.marketplace_url}/v1/keys`, {
2518
+ method: "POST",
2519
+ headers: {
2520
+ "Content-Type": "application/json",
2521
+ Authorization: `Bearer ${config.auth_token}`
2522
+ },
2523
+ body: JSON.stringify({ public_key_pem: publicKey, label: "default" })
2524
+ });
2525
+ if (res.ok || res.status === 409) {
2526
+ console.log(chalk9.dim(" Public key registered with marketplace."));
2527
+ return true;
2528
+ }
2529
+ const body = await res.json().catch(() => ({}));
2530
+ console.log(chalk9.yellow(` Warning: Could not register public key: ${body.error || res.statusText}`));
2531
+ return false;
2532
+ } catch {
2533
+ console.log(chalk9.yellow(" Warning: Could not reach marketplace to register public key."));
2534
+ return false;
2535
+ }
2536
+ }
2537
+
2538
+ // src/commands/login.ts
2507
2539
  var DEFAULT_HOST = "127.0.0.1";
2508
2540
  function listenOnPort(server, port, host) {
2509
2541
  return new Promise((resolve, reject) => {
@@ -2517,8 +2549,8 @@ function listenOnPort(server, port, host) {
2517
2549
  }
2518
2550
  async function loginCommand(options) {
2519
2551
  const config = loadConfig();
2520
- console.log(chalk9.bold("SkillPort Market Login"));
2521
- console.log(chalk9.dim(`Marketplace: ${config.marketplace_web_url}`));
2552
+ console.log(chalk10.bold("SkillPort Market Login"));
2553
+ console.log(chalk10.dim(`Marketplace: ${config.marketplace_web_url}`));
2522
2554
  console.log();
2523
2555
  let method = options.method;
2524
2556
  if (options.token) {
@@ -2531,8 +2563,8 @@ async function loginCommand(options) {
2531
2563
  name: "method",
2532
2564
  message: "Login method:",
2533
2565
  choices: [
2534
- { name: "Browser (GitHub OAuth)", value: "browser" },
2535
- { name: "Paste API token", value: "token" }
2566
+ { name: "Paste CLI token (recommended)", value: "token" },
2567
+ { name: "Browser (GitHub OAuth)", value: "browser" }
2536
2568
  ]
2537
2569
  }
2538
2570
  ]);
@@ -2540,15 +2572,24 @@ async function loginCommand(options) {
2540
2572
  }
2541
2573
  if (method === "token") {
2542
2574
  let token2 = options.token;
2575
+ if (!token2 && options.yes) {
2576
+ console.error(chalk10.red("Error: --token is required in non-interactive mode."));
2577
+ console.log(chalk10.dim(" Get your token at: https://skillport.market/auth/cli-token"));
2578
+ process.exitCode = 1;
2579
+ return;
2580
+ }
2543
2581
  if (!token2) {
2582
+ console.log(chalk10.dim("Get your token at: https://skillport.market/auth/cli-token"));
2583
+ console.log();
2544
2584
  const answer = await inquirer4.prompt([
2545
- { type: "password", name: "token", message: "Enter your API token:" }
2585
+ { type: "password", name: "token", message: "Enter your CLI token:" }
2546
2586
  ]);
2547
2587
  token2 = answer.token;
2548
2588
  }
2549
2589
  config.auth_token = token2;
2550
2590
  saveConfig(config);
2551
- console.log(chalk9.green("Login successful! Token saved."));
2591
+ await registerPublicKey(config);
2592
+ console.log(chalk10.green("Login successful! Token saved."));
2552
2593
  return;
2553
2594
  }
2554
2595
  const state = randomBytes(16).toString("hex");
@@ -2562,7 +2603,7 @@ async function loginCommand(options) {
2562
2603
  } catch (err) {
2563
2604
  const code = err.code;
2564
2605
  if (code === "EADDRINUSE" && !userExplicitPort) {
2565
- console.log(chalk9.yellow(`Port ${requestedPort} in use, selecting a free port...`));
2606
+ console.log(chalk10.yellow(`Port ${requestedPort} in use, selecting a free port...`));
2566
2607
  actualPort = await listenOnPort(server, 0, bindHost);
2567
2608
  } else {
2568
2609
  throw err;
@@ -2571,16 +2612,16 @@ async function loginCommand(options) {
2571
2612
  const callbackHost = bindHost === "::1" ? "[::1]" : bindHost;
2572
2613
  const authUrl = `${config.marketplace_web_url}/auth/cli?state=${state}&port=${actualPort}&host=${encodeURIComponent(callbackHost)}`;
2573
2614
  if (options.browser === false) {
2574
- console.log(chalk9.bold("Open this URL in your browser to authenticate:"));
2615
+ console.log(chalk10.bold("Open this URL in your browser to authenticate:"));
2575
2616
  console.log();
2576
2617
  console.log(` ${authUrl}`);
2577
2618
  console.log();
2578
- console.log(chalk9.dim(`Listening on ${callbackHost}:${actualPort}`));
2579
- console.log(chalk9.dim("Waiting for authentication callback..."));
2619
+ console.log(chalk10.dim(`Listening on ${callbackHost}:${actualPort}`));
2620
+ console.log(chalk10.dim("Waiting for authentication callback..."));
2580
2621
  } else {
2581
- console.log(chalk9.dim(`Opening browser to: ${authUrl}`));
2582
- console.log(chalk9.dim(`Listening on ${callbackHost}:${actualPort}`));
2583
- console.log(chalk9.dim("Waiting for authentication..."));
2622
+ console.log(chalk10.dim(`Opening browser to: ${authUrl}`));
2623
+ console.log(chalk10.dim(`Listening on ${callbackHost}:${actualPort}`));
2624
+ console.log(chalk10.dim("Waiting for authentication..."));
2584
2625
  const { exec } = await import("node:child_process");
2585
2626
  const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2586
2627
  exec(`${openCmd} "${authUrl}"`);
@@ -2638,34 +2679,20 @@ async function loginCommand(options) {
2638
2679
  config.auth_token = token;
2639
2680
  }
2640
2681
  saveConfig(config);
2641
- if (hasKeys()) {
2642
- try {
2643
- const publicKey = loadPublicKey2();
2644
- await fetch(`${config.marketplace_url}/v1/keys`, {
2645
- method: "POST",
2646
- headers: {
2647
- "Content-Type": "application/json",
2648
- Authorization: `Bearer ${config.auth_token}`
2649
- },
2650
- body: JSON.stringify({ public_key_pem: publicKey, label: "default" })
2651
- });
2652
- console.log(chalk9.dim(" Public key registered with marketplace."));
2653
- } catch {
2654
- }
2655
- }
2656
- console.log(chalk9.green("Login successful! Token saved."));
2682
+ await registerPublicKey(config);
2683
+ console.log(chalk10.green("Login successful! Token saved."));
2657
2684
  }
2658
2685
 
2659
2686
  // src/commands/publish.ts
2660
2687
  import { readFileSync as readFileSync8, existsSync as existsSync5 } from "node:fs";
2661
2688
  import { join as join6 } from "node:path";
2662
2689
  import { homedir as homedir3 } from "node:os";
2663
- import chalk10 from "chalk";
2690
+ import chalk11 from "chalk";
2664
2691
  init_config();
2665
2692
  async function publishCommand(sspPath) {
2666
2693
  const config = loadConfig();
2667
2694
  if (!config.auth_token) {
2668
- console.log(chalk10.red("Not logged in. Run 'skillport login' first."));
2695
+ console.log(chalk11.red("Not logged in. Run 'skillport login' first."));
2669
2696
  process.exitCode = 1;
2670
2697
  return;
2671
2698
  }
@@ -2674,12 +2701,12 @@ async function publishCommand(sspPath) {
2674
2701
  const extracted = await extractSSP(data);
2675
2702
  const { valid } = verifyChecksums(extracted.files, extracted.checksums);
2676
2703
  if (!valid) {
2677
- console.log(chalk10.red("Checksum verification failed. Cannot publish."));
2704
+ console.log(chalk11.red("Checksum verification failed. Cannot publish."));
2678
2705
  process.exitCode = 1;
2679
2706
  return;
2680
2707
  }
2681
2708
  if (!extracted.authorSignature) {
2682
- console.log(chalk10.red("No author signature. Sign the package first."));
2709
+ console.log(chalk11.red("No author signature. Sign the package first."));
2683
2710
  process.exitCode = 1;
2684
2711
  return;
2685
2712
  }
@@ -2693,53 +2720,425 @@ async function publishCommand(sspPath) {
2693
2720
  pubKeyPem
2694
2721
  );
2695
2722
  if (!sigValid) {
2696
- console.log(chalk10.red("Signature verification failed. Package may have been tampered with after signing."));
2697
- console.log(chalk10.dim(` Key ID: ${keyId}`));
2723
+ console.log(chalk11.red("Signature verification failed. Package may have been tampered with after signing."));
2724
+ console.log(chalk11.dim(` Key ID: ${keyId}`));
2698
2725
  process.exitCode = 1;
2699
2726
  return;
2700
2727
  }
2701
- console.log(chalk10.green("\u2713 Signature verified"));
2728
+ console.log(chalk11.green("\u2713 Signature verified"));
2702
2729
  } else {
2703
- console.log(chalk10.yellow("\u26A0 Local public key not found \u2014 skipping local signature check"));
2704
- console.log(chalk10.dim(" Server will verify signature against registered key."));
2730
+ console.log(chalk11.yellow("\u26A0 Local public key not found \u2014 skipping local signature check"));
2731
+ console.log(chalk11.dim(" Server will verify signature against registered key."));
2705
2732
  }
2706
2733
  console.log("Uploading to marketplace...");
2707
- try {
2734
+ async function upload() {
2708
2735
  const formData = new FormData();
2709
2736
  formData.append("file", new Blob([data]), sspPath.split("/").pop());
2710
- const response = await fetch(`${config.marketplace_url}/v1/skills`, {
2737
+ return fetch(`${config.marketplace_url}/v1/skills`, {
2711
2738
  method: "POST",
2712
- headers: {
2713
- Authorization: `Bearer ${config.auth_token}`
2714
- },
2739
+ headers: { Authorization: `Bearer ${config.auth_token}` },
2715
2740
  body: formData
2716
2741
  });
2742
+ }
2743
+ try {
2744
+ let response = await upload();
2717
2745
  if (!response.ok) {
2718
- const errorBody = await response.json();
2719
- console.log(chalk10.red(`Upload failed: ${errorBody.error || response.statusText}`));
2746
+ const errorBody = await response.json().catch(() => ({}));
2747
+ const errorMsg = String(errorBody.error || "");
2748
+ if (errorMsg.includes("Signing key is not registered") && hasKeys()) {
2749
+ console.log(chalk11.yellow("Signing key not registered. Registering automatically..."));
2750
+ const registered = await registerPublicKey(config);
2751
+ if (registered) {
2752
+ console.log("Retrying upload...");
2753
+ response = await upload();
2754
+ } else {
2755
+ console.log(chalk11.red("Could not register key. Run 'skillport keys register' manually."));
2756
+ process.exitCode = 1;
2757
+ return;
2758
+ }
2759
+ }
2760
+ if (!response.ok) {
2761
+ const retryBody = await response.json().catch(() => ({}));
2762
+ console.log(chalk11.red(`Upload failed: ${retryBody.error || response.statusText}`));
2763
+ process.exitCode = 1;
2764
+ return;
2765
+ }
2766
+ }
2767
+ const result = await response.json();
2768
+ if (result.status === "published") {
2769
+ console.log(chalk11.green("Version updated successfully!"));
2770
+ } else {
2771
+ console.log(chalk11.green("Uploaded as draft."));
2772
+ }
2773
+ console.log();
2774
+ console.log(` ${chalk11.bold("Skill ID:")} ${result.id}`);
2775
+ console.log(` ${chalk11.bold("SSP ID:")} ${result.ssp_id}`);
2776
+ console.log(` ${chalk11.bold("Version:")} ${result.version}`);
2777
+ console.log(` ${chalk11.bold("Status:")} ${result.status === "published" ? chalk11.green("published") : chalk11.yellow(result.status || "draft")}`);
2778
+ console.log(` ${chalk11.bold("Scan:")} ${result.scan_passed ? chalk11.green("PASSED") : chalk11.red("FAILED")}`);
2779
+ console.log(` ${chalk11.bold("Risk Score:")} ${result.risk_score}/100`);
2780
+ console.log();
2781
+ if (result.status === "published") {
2782
+ console.log(chalk11.dim(` URL: ${config.marketplace_web_url}/skills/${result.id}`));
2783
+ console.log(chalk11.dim(` Install: skillport install ${result.ssp_id}@${result.version}`));
2784
+ } else {
2785
+ console.log(chalk11.dim(` Go to Dashboard to publish: ${config.marketplace_web_url}/dashboard`));
2786
+ }
2787
+ } catch (error) {
2788
+ console.log(chalk11.red(`Upload failed: ${error.message}`));
2789
+ process.exitCode = 1;
2790
+ }
2791
+ }
2792
+
2793
+ // src/commands/whoami.ts
2794
+ import { existsSync as existsSync6 } from "node:fs";
2795
+ import chalk12 from "chalk";
2796
+ init_config();
2797
+ function gather() {
2798
+ const cfgPath = configPath();
2799
+ const cfgExists = existsSync6(cfgPath);
2800
+ const config = loadConfig();
2801
+ const keysExist = hasKeys();
2802
+ let localKeyId = null;
2803
+ if (keysExist) {
2804
+ try {
2805
+ const pem = loadPublicKey2();
2806
+ localKeyId = computeKeyId(pem);
2807
+ } catch {
2808
+ }
2809
+ }
2810
+ return {
2811
+ config_path: cfgPath,
2812
+ config_exists: cfgExists,
2813
+ marketplace_url: config.marketplace_url,
2814
+ marketplace_web_url: config.marketplace_web_url,
2815
+ authenticated: !!config.auth_token,
2816
+ default_key_id: config.default_key_id ?? null,
2817
+ keys_exist: keysExist,
2818
+ local_key_id: localKeyId
2819
+ };
2820
+ }
2821
+ function whoamiCommand(opts) {
2822
+ const info = gather();
2823
+ if (opts.json) {
2824
+ console.log(JSON.stringify(info, null, 2));
2825
+ return;
2826
+ }
2827
+ console.log(chalk12.bold("SkillPort CLI"));
2828
+ console.log();
2829
+ console.log(` ${chalk12.bold("Config:")} ${info.config_exists ? chalk12.green(info.config_path) : chalk12.yellow("not created yet")}`);
2830
+ console.log(` ${chalk12.bold("API:")} ${info.marketplace_url}`);
2831
+ console.log(` ${chalk12.bold("Web:")} ${info.marketplace_web_url}`);
2832
+ console.log(` ${chalk12.bold("Authenticated:")} ${info.authenticated ? chalk12.green("yes") : chalk12.red("no")}`);
2833
+ console.log(` ${chalk12.bold("Signing keys:")} ${info.keys_exist ? chalk12.green("present") : chalk12.red("not found")}`);
2834
+ if (info.local_key_id) {
2835
+ console.log(` ${chalk12.bold("Key ID:")} ${info.local_key_id}`);
2836
+ }
2837
+ if (info.default_key_id) {
2838
+ console.log(` ${chalk12.bold("Default key:")} ${info.default_key_id}`);
2839
+ }
2840
+ }
2841
+
2842
+ // src/commands/doctor.ts
2843
+ import { existsSync as existsSync7 } from "node:fs";
2844
+ import chalk13 from "chalk";
2845
+ init_config();
2846
+ async function fetchHealth(url, timeoutMs = 5e3) {
2847
+ try {
2848
+ const controller = new AbortController();
2849
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2850
+ const res = await fetch(`${url}/health`, { signal: controller.signal });
2851
+ clearTimeout(timer);
2852
+ return res.ok;
2853
+ } catch {
2854
+ return false;
2855
+ }
2856
+ }
2857
+ async function fetchWebReachable(url, timeoutMs = 5e3) {
2858
+ try {
2859
+ const controller = new AbortController();
2860
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2861
+ const res = await fetch(url, {
2862
+ method: "HEAD",
2863
+ signal: controller.signal
2864
+ });
2865
+ clearTimeout(timer);
2866
+ return res.ok || res.status === 308 || res.status === 307;
2867
+ } catch {
2868
+ return false;
2869
+ }
2870
+ }
2871
+ async function runChecks() {
2872
+ const checks = [];
2873
+ const config = loadConfig();
2874
+ const cfgExists = existsSync7(configPath());
2875
+ checks.push({
2876
+ name: "config",
2877
+ status: cfgExists ? "ok" : "warn",
2878
+ message: cfgExists ? `Found: ${configPath()}` : "No config file (using defaults)"
2879
+ });
2880
+ checks.push({
2881
+ name: "auth",
2882
+ status: config.auth_token ? "ok" : "warn",
2883
+ message: config.auth_token ? "Authenticated" : "Not logged in \u2014 run 'skillport login'"
2884
+ });
2885
+ const keysExist = hasKeys();
2886
+ let keyMsg = "Not found \u2014 run 'skillport init'";
2887
+ if (keysExist) {
2888
+ try {
2889
+ const pem = loadPublicKey2();
2890
+ const kid = computeKeyId(pem);
2891
+ keyMsg = `Present (key ID: ${kid})`;
2892
+ } catch {
2893
+ keyMsg = "Files exist but unreadable";
2894
+ }
2895
+ }
2896
+ checks.push({
2897
+ name: "keys",
2898
+ status: keysExist ? "ok" : "warn",
2899
+ message: keyMsg
2900
+ });
2901
+ const apiOk = await fetchHealth(config.marketplace_url);
2902
+ checks.push({
2903
+ name: "api",
2904
+ status: apiOk ? "ok" : "fail",
2905
+ message: apiOk ? `Reachable: ${config.marketplace_url}` : `Unreachable: ${config.marketplace_url}`
2906
+ });
2907
+ const webOk = await fetchWebReachable(config.marketplace_web_url);
2908
+ checks.push({
2909
+ name: "web",
2910
+ status: webOk ? "ok" : "fail",
2911
+ message: webOk ? `Reachable: ${config.marketplace_web_url}` : `Unreachable: ${config.marketplace_web_url}`
2912
+ });
2913
+ if (config.auth_token && keysExist) {
2914
+ try {
2915
+ const pem = loadPublicKey2();
2916
+ const kid = computeKeyId(pem);
2917
+ const controller = new AbortController();
2918
+ const timer = setTimeout(() => controller.abort(), 5e3);
2919
+ const res = await fetch(`${config.marketplace_url}/v1/keys`, {
2920
+ method: "POST",
2921
+ headers: {
2922
+ "Content-Type": "application/json",
2923
+ Authorization: `Bearer ${config.auth_token}`
2924
+ },
2925
+ body: JSON.stringify({ public_key_pem: pem, label: "default" }),
2926
+ signal: controller.signal
2927
+ });
2928
+ clearTimeout(timer);
2929
+ if (res.ok || res.status === 409) {
2930
+ checks.push({
2931
+ name: "key_registered",
2932
+ status: "ok",
2933
+ message: `Key ${kid} is registered on marketplace`
2934
+ });
2935
+ } else {
2936
+ const body = await res.json().catch(() => ({}));
2937
+ checks.push({
2938
+ name: "key_registered",
2939
+ status: "warn",
2940
+ message: `Key registration check failed: ${body.error || res.statusText}`
2941
+ });
2942
+ }
2943
+ } catch {
2944
+ checks.push({
2945
+ name: "key_registered",
2946
+ status: "warn",
2947
+ message: "Could not verify key registration (API unreachable)"
2948
+ });
2949
+ }
2950
+ }
2951
+ const ok = checks.every((c) => c.status !== "fail");
2952
+ return { checks, ok };
2953
+ }
2954
+ async function doctorCommand(opts) {
2955
+ if (!opts.json) {
2956
+ console.log(chalk13.bold("SkillPort Doctor"));
2957
+ console.log();
2958
+ }
2959
+ const result = await runChecks();
2960
+ if (opts.json) {
2961
+ console.log(JSON.stringify(result, null, 2));
2962
+ } else {
2963
+ for (const check of result.checks) {
2964
+ const icon = check.status === "ok" ? chalk13.green("OK") : check.status === "warn" ? chalk13.yellow("WARN") : chalk13.red("FAIL");
2965
+ console.log(` [${icon}] ${chalk13.bold(check.name)}: ${check.message}`);
2966
+ }
2967
+ console.log();
2968
+ if (result.ok) {
2969
+ console.log(chalk13.green("All critical checks passed."));
2970
+ } else {
2971
+ console.log(chalk13.red("Some checks failed. See above for details."));
2972
+ process.exitCode = 1;
2973
+ }
2974
+ }
2975
+ }
2976
+
2977
+ // src/commands/keys-register.ts
2978
+ init_config();
2979
+ import chalk14 from "chalk";
2980
+ async function keysRegisterCommand() {
2981
+ const config = loadConfig();
2982
+ if (!config.auth_token) {
2983
+ console.log(chalk14.red("Not logged in. Run 'skillport login' first."));
2984
+ process.exitCode = 1;
2985
+ return;
2986
+ }
2987
+ if (!hasKeys()) {
2988
+ console.log(chalk14.red("No signing keys found. Run 'skillport init' first."));
2989
+ process.exitCode = 1;
2990
+ return;
2991
+ }
2992
+ const ok = await registerPublicKey(config);
2993
+ if (!ok) {
2994
+ process.exitCode = 1;
2995
+ }
2996
+ }
2997
+
2998
+ // src/commands/list.ts
2999
+ init_config();
3000
+ import chalk15 from "chalk";
3001
+ var STATUS_COLORS = {
3002
+ published: chalk15.green,
3003
+ draft: chalk15.yellow,
3004
+ archived: chalk15.gray,
3005
+ suspended: chalk15.red,
3006
+ pending_review: chalk15.cyan
3007
+ };
3008
+ var STATUS_LABELS = {
3009
+ published: "published",
3010
+ draft: "draft",
3011
+ archived: "deleted",
3012
+ suspended: "suspended",
3013
+ pending_review: "pending"
3014
+ };
3015
+ async function listCommand(opts) {
3016
+ const config = loadConfig();
3017
+ if (!config.auth_token) {
3018
+ console.log(chalk15.red("Not logged in. Run 'skillport login' first."));
3019
+ process.exitCode = 1;
3020
+ return;
3021
+ }
3022
+ try {
3023
+ const res = await fetch(`${config.marketplace_url}/v1/me/skills`, {
3024
+ headers: { Authorization: `Bearer ${config.auth_token}` }
3025
+ });
3026
+ if (!res.ok) {
3027
+ const body = await res.json().catch(() => ({}));
3028
+ console.log(chalk15.red(`Failed to fetch skills: ${body.error || res.statusText}`));
2720
3029
  process.exitCode = 1;
2721
3030
  return;
2722
3031
  }
2723
- const result = await response.json();
2724
- console.log(chalk10.green("Published successfully!"));
3032
+ const skills = await res.json();
3033
+ if (opts.json) {
3034
+ console.log(JSON.stringify(skills, null, 2));
3035
+ return;
3036
+ }
3037
+ if (skills.length === 0) {
3038
+ console.log(chalk15.dim("No skills found. Publish your first skill with:"));
3039
+ console.log(chalk15.dim(" skillport export ./my-skill -o my-skill.ssp && skillport publish my-skill.ssp"));
3040
+ return;
3041
+ }
3042
+ console.log(chalk15.bold(`Your Skills (${skills.length})`));
2725
3043
  console.log();
2726
- console.log(` ${chalk10.bold("Skill ID:")} ${result.id}`);
2727
- console.log(` ${chalk10.bold("SSP ID:")} ${result.ssp_id}`);
2728
- console.log(` ${chalk10.bold("Version:")} ${result.version}`);
2729
- console.log(` ${chalk10.bold("Scan:")} ${result.scan_passed ? chalk10.green("PASSED") : chalk10.red("FAILED")}`);
2730
- console.log(` ${chalk10.bold("Risk Score:")} ${result.risk_score}/100`);
3044
+ const idWidth = Math.max(4, ...skills.map((s) => s.ssp_id.length));
3045
+ const titleWidth = Math.max(5, ...skills.map((s) => s.title.length));
3046
+ const statusWidth = 10;
3047
+ const versionWidth = 8;
3048
+ const dlWidth = 5;
3049
+ console.log(
3050
+ chalk15.dim(
3051
+ " " + "SSP ID".padEnd(idWidth + 2) + "TITLE".padEnd(titleWidth + 2) + "STATUS".padEnd(statusWidth + 2) + "VERSION".padEnd(versionWidth + 2) + "DL".padEnd(dlWidth + 2) + "ID"
3052
+ )
3053
+ );
3054
+ for (const skill of skills) {
3055
+ const colorFn = STATUS_COLORS[skill.status] || chalk15.white;
3056
+ const label = STATUS_LABELS[skill.status] || skill.status;
3057
+ console.log(
3058
+ " " + skill.ssp_id.padEnd(idWidth + 2) + skill.title.padEnd(titleWidth + 2) + colorFn(label.padEnd(statusWidth + 2)) + (skill.latest_version || "\u2014").padEnd(versionWidth + 2) + String(skill.downloads || 0).padEnd(dlWidth + 2) + chalk15.dim(skill.id)
3059
+ );
3060
+ }
2731
3061
  console.log();
2732
- console.log(chalk10.dim(` URL: ${config.marketplace_web_url}/skills/${result.id}`));
2733
- console.log(chalk10.dim(` Install: skillport install ${result.ssp_id}@${result.version}`));
3062
+ console.log(chalk15.dim(" Manage: skillport manage <skill-id> publish|unpublish|delete"));
3063
+ } catch (error) {
3064
+ console.log(chalk15.red(`Failed to fetch skills: ${error.message}`));
3065
+ process.exitCode = 1;
3066
+ }
3067
+ }
3068
+
3069
+ // src/commands/manage.ts
3070
+ init_config();
3071
+ import chalk16 from "chalk";
3072
+ var ACTION_DESC = {
3073
+ publish: "Publishing skill (draft \u2192 published)...",
3074
+ unpublish: "Unpublishing skill (published \u2192 draft)...",
3075
+ delete: "Deleting skill..."
3076
+ };
3077
+ var ACTION_SUCCESS = {
3078
+ publish: "Skill published! It is now live on the marketplace.",
3079
+ unpublish: "Skill unpublished. It is now in draft.",
3080
+ delete: "Skill deleted. It is no longer visible on the marketplace."
3081
+ };
3082
+ async function manageCommand(skillId, action) {
3083
+ const validActions = ["publish", "unpublish", "delete"];
3084
+ if (!validActions.includes(action)) {
3085
+ console.log(chalk16.red(`Invalid action: ${action}`));
3086
+ console.log(`Valid actions: ${validActions.join(", ")}`);
3087
+ process.exitCode = 1;
3088
+ return;
3089
+ }
3090
+ const config = loadConfig();
3091
+ if (!config.auth_token) {
3092
+ console.log(chalk16.red("Not logged in. Run 'skillport login' first."));
3093
+ process.exitCode = 1;
3094
+ return;
3095
+ }
3096
+ const act = action;
3097
+ console.log(ACTION_DESC[act]);
3098
+ try {
3099
+ let res;
3100
+ if (act === "delete") {
3101
+ res = await fetch(`${config.marketplace_url}/v1/skills/${skillId}`, {
3102
+ method: "DELETE",
3103
+ headers: { Authorization: `Bearer ${config.auth_token}` }
3104
+ });
3105
+ } else {
3106
+ res = await fetch(`${config.marketplace_url}/v1/skills/${skillId}/${act}`, {
3107
+ method: "POST",
3108
+ headers: { Authorization: `Bearer ${config.auth_token}` }
3109
+ });
3110
+ }
3111
+ if (!res.ok) {
3112
+ const body = await res.json().catch(() => ({}));
3113
+ const msg = String(body.error || res.statusText);
3114
+ if (res.status === 403) {
3115
+ console.log(chalk16.red("Permission denied. You are not the author of this skill."));
3116
+ } else if (res.status === 404) {
3117
+ console.log(chalk16.red(`Skill not found: ${skillId}`));
3118
+ } else {
3119
+ console.log(chalk16.red(`Failed: ${msg}`));
3120
+ }
3121
+ process.exitCode = 1;
3122
+ return;
3123
+ }
3124
+ const result = await res.json();
3125
+ console.log(chalk16.green(ACTION_SUCCESS[act]));
3126
+ console.log();
3127
+ console.log(` ${chalk16.bold("Skill ID:")} ${result.id}`);
3128
+ console.log(` ${chalk16.bold("Status:")} ${result.status}`);
3129
+ if (act === "publish") {
3130
+ console.log();
3131
+ console.log(chalk16.dim(` URL: ${config.marketplace_web_url}/skills/${result.id}`));
3132
+ }
2734
3133
  } catch (error) {
2735
- console.log(chalk10.red(`Upload failed: ${error.message}`));
3134
+ console.log(chalk16.red(`Failed: ${error.message}`));
2736
3135
  process.exitCode = 1;
2737
3136
  }
2738
3137
  }
2739
3138
 
2740
3139
  // src/index.ts
2741
3140
  var program = new Command();
2742
- program.name("skillport").description("SkillPort \u2014 secure skill distribution for OpenClaw").version("0.1.4");
3141
+ program.name("skillport").description("SkillPort \u2014 open-source secure skill distribution for OpenClaw").version("0.1.7");
2743
3142
  program.command("init").description("Generate Ed25519 key pair for signing").action(initCommand);
2744
3143
  program.command("scan <path>").description("Run security scan on a skill directory or .ssp file").action(scanCommand);
2745
3144
  program.command("export <path>").description("Export a skill directory as a SkillPort package (.ssp)").option("-o, --output <file>", "Output file path").option("-y, --yes", "Non-interactive mode (include all, skip prompts)").option("--id <id>", "Skill ID (author-slug/skill-slug)").option("--name <name>", "Skill name").option("--description <desc>", "Skill description").option("--skill-version <ver>", "Skill version (semver)").option("--author <name>", "Author name").option("--openclaw-compat <range>", "OpenClaw compatibility range").option("--os <os...>", "Compatible OS (macos, linux, windows)").action(exportCommand);
@@ -2750,4 +3149,10 @@ program.command("dry-run <ssp>").description("Run installation diagnostics witho
2750
3149
  program.command("uninstall <id>").description("Uninstall an installed skill").action(uninstallCommand);
2751
3150
  program.command("login").description("Authenticate with SkillPort Market").option("--method <method>", "Login method: browser or token", "browser").option("--token <token>", "API token (for --method token)").option("-y, --yes", "Non-interactive mode (skip prompts)").option("--no-browser", "Print auth URL instead of opening browser").option("--port <port>", "Callback port (default: 9876, use 0 for auto)").option("--host <host>", "Callback host (default: 127.0.0.1)").action(loginCommand);
2752
3151
  program.command("publish <ssp>").description("Publish a SkillPort package to the marketplace").action(publishCommand);
3152
+ program.command("list").description("List your marketplace skills and their status").option("--json", "Output as JSON").action(listCommand);
3153
+ program.command("manage <skill-id> <action>").description("Manage a skill: publish, unpublish, or delete").action(manageCommand);
3154
+ program.command("whoami").description("Show current configuration and identity").option("--json", "Output as JSON").action(whoamiCommand);
3155
+ program.command("doctor").description("Check connectivity and setup health").option("--json", "Output as JSON").action(doctorCommand);
3156
+ var keys = program.command("keys").description("Manage signing keys");
3157
+ keys.command("register").description("Register your public signing key with the marketplace").action(keysRegisterCommand);
2753
3158
  program.parse();
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@skillport/cli",
3
- "version": "0.1.4",
4
- "description": "SkillPort CLI — secure skill distribution for OpenClaw. Export, scan, sign, and install AI skill packages.",
3
+ "version": "0.1.7",
4
+ "description": "SkillPort CLI — open-source secure skill distribution for OpenClaw. Export, scan, sign, and install AI skill packages.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/my-name-is-yu/skillport.git",
9
+ "url": "https://github.com/skillport-dev/skillport.git",
10
10
  "directory": "apps/cli"
11
11
  },
12
12
  "homepage": "https://skillport.market",
13
- "bugs": "https://github.com/my-name-is-yu/skillport/issues",
13
+ "bugs": "https://github.com/skillport-dev/skillport/issues",
14
14
  "keywords": [
15
15
  "skillport",
16
16
  "openclaw",