@skillport/cli 0.1.3 → 0.1.6

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 +142 -64
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2502,12 +2502,45 @@ 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
- function listenOnPort(server, port) {
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
2539
+ var DEFAULT_HOST = "127.0.0.1";
2540
+ function listenOnPort(server, port, host) {
2508
2541
  return new Promise((resolve, reject) => {
2509
2542
  server.once("error", reject);
2510
- server.listen(port, () => {
2543
+ server.listen(port, host, () => {
2511
2544
  server.removeListener("error", reject);
2512
2545
  const addr = server.address();
2513
2546
  resolve(addr.port);
@@ -2516,8 +2549,8 @@ function listenOnPort(server, port) {
2516
2549
  }
2517
2550
  async function loginCommand(options) {
2518
2551
  const config = loadConfig();
2519
- console.log(chalk9.bold("SkillPort Market Login"));
2520
- 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}`));
2521
2554
  console.log();
2522
2555
  let method = options.method;
2523
2556
  if (options.token) {
@@ -2530,8 +2563,8 @@ async function loginCommand(options) {
2530
2563
  name: "method",
2531
2564
  message: "Login method:",
2532
2565
  choices: [
2533
- { name: "Browser (GitHub OAuth)", value: "browser" },
2534
- { name: "Paste API token", value: "token" }
2566
+ { name: "Paste CLI token (recommended)", value: "token" },
2567
+ { name: "Browser (GitHub OAuth)", value: "browser" }
2535
2568
  ]
2536
2569
  }
2537
2570
  ]);
@@ -2539,43 +2572,56 @@ async function loginCommand(options) {
2539
2572
  }
2540
2573
  if (method === "token") {
2541
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
+ }
2542
2581
  if (!token2) {
2582
+ console.log(chalk10.dim("Get your token at: https://skillport.market/auth/cli-token"));
2583
+ console.log();
2543
2584
  const answer = await inquirer4.prompt([
2544
- { type: "password", name: "token", message: "Enter your API token:" }
2585
+ { type: "password", name: "token", message: "Enter your CLI token:" }
2545
2586
  ]);
2546
2587
  token2 = answer.token;
2547
2588
  }
2548
2589
  config.auth_token = token2;
2549
2590
  saveConfig(config);
2550
- console.log(chalk9.green("Login successful! Token saved."));
2591
+ await registerPublicKey(config);
2592
+ console.log(chalk10.green("Login successful! Token saved."));
2551
2593
  return;
2552
2594
  }
2553
2595
  const state = randomBytes(16).toString("hex");
2554
2596
  const requestedPort = options.port !== void 0 ? parseInt(options.port, 10) : 9876;
2555
2597
  const userExplicitPort = options.port !== void 0;
2598
+ const bindHost = options.host || DEFAULT_HOST;
2556
2599
  const server = createServer();
2557
2600
  let actualPort;
2558
2601
  try {
2559
- actualPort = await listenOnPort(server, requestedPort);
2602
+ actualPort = await listenOnPort(server, requestedPort, bindHost);
2560
2603
  } catch (err) {
2561
2604
  const code = err.code;
2562
2605
  if (code === "EADDRINUSE" && !userExplicitPort) {
2563
- console.log(chalk9.yellow(`Port ${requestedPort} in use, selecting a free port...`));
2564
- actualPort = await listenOnPort(server, 0);
2606
+ console.log(chalk10.yellow(`Port ${requestedPort} in use, selecting a free port...`));
2607
+ actualPort = await listenOnPort(server, 0, bindHost);
2565
2608
  } else {
2566
2609
  throw err;
2567
2610
  }
2568
2611
  }
2569
- const authUrl = `${config.marketplace_web_url}/auth/cli?state=${state}&port=${actualPort}`;
2612
+ const callbackHost = bindHost === "::1" ? "[::1]" : bindHost;
2613
+ const authUrl = `${config.marketplace_web_url}/auth/cli?state=${state}&port=${actualPort}&host=${encodeURIComponent(callbackHost)}`;
2570
2614
  if (options.browser === false) {
2571
- 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:"));
2572
2616
  console.log();
2573
2617
  console.log(` ${authUrl}`);
2574
2618
  console.log();
2575
- 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..."));
2576
2621
  } else {
2577
- console.log(chalk9.dim(`Opening browser to: ${authUrl}`));
2578
- 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..."));
2579
2625
  const { exec } = await import("node:child_process");
2580
2626
  const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2581
2627
  exec(`${openCmd} "${authUrl}"`);
@@ -2583,10 +2629,16 @@ async function loginCommand(options) {
2583
2629
  const token = await new Promise((resolve, reject) => {
2584
2630
  const timeout = setTimeout(() => {
2585
2631
  server.close();
2586
- reject(new Error("Authentication timed out (60s). Try again or use: skillport login --method token --token <your-token>"));
2632
+ reject(new Error(
2633
+ `Authentication timed out (60s).
2634
+ Listened on: ${callbackHost}:${actualPort}
2635
+ Self-test: curl http://${callbackHost}:${actualPort}/callback?state=test\\&token=test
2636
+ Retry: skillport login --yes --no-browser --port 0 --host 127.0.0.1
2637
+ Or use: skillport login --method token --token <your-token>`
2638
+ ));
2587
2639
  }, 6e4);
2588
2640
  server.on("request", (req, res) => {
2589
- const url = new URL(req.url || "", `http://localhost:${actualPort}`);
2641
+ const url = new URL(req.url || "", `http://${callbackHost}:${actualPort}`);
2590
2642
  if (url.pathname === "/callback") {
2591
2643
  const callbackState = url.searchParams.get("state");
2592
2644
  const accessToken = url.searchParams.get("token");
@@ -2627,34 +2679,20 @@ async function loginCommand(options) {
2627
2679
  config.auth_token = token;
2628
2680
  }
2629
2681
  saveConfig(config);
2630
- if (hasKeys()) {
2631
- try {
2632
- const publicKey = loadPublicKey2();
2633
- await fetch(`${config.marketplace_url}/v1/keys`, {
2634
- method: "POST",
2635
- headers: {
2636
- "Content-Type": "application/json",
2637
- Authorization: `Bearer ${config.auth_token}`
2638
- },
2639
- body: JSON.stringify({ public_key_pem: publicKey, label: "default" })
2640
- });
2641
- console.log(chalk9.dim(" Public key registered with marketplace."));
2642
- } catch {
2643
- }
2644
- }
2645
- console.log(chalk9.green("Login successful! Token saved."));
2682
+ await registerPublicKey(config);
2683
+ console.log(chalk10.green("Login successful! Token saved."));
2646
2684
  }
2647
2685
 
2648
2686
  // src/commands/publish.ts
2649
2687
  import { readFileSync as readFileSync8, existsSync as existsSync5 } from "node:fs";
2650
2688
  import { join as join6 } from "node:path";
2651
2689
  import { homedir as homedir3 } from "node:os";
2652
- import chalk10 from "chalk";
2690
+ import chalk11 from "chalk";
2653
2691
  init_config();
2654
2692
  async function publishCommand(sspPath) {
2655
2693
  const config = loadConfig();
2656
2694
  if (!config.auth_token) {
2657
- console.log(chalk10.red("Not logged in. Run 'skillport login' first."));
2695
+ console.log(chalk11.red("Not logged in. Run 'skillport login' first."));
2658
2696
  process.exitCode = 1;
2659
2697
  return;
2660
2698
  }
@@ -2663,12 +2701,12 @@ async function publishCommand(sspPath) {
2663
2701
  const extracted = await extractSSP(data);
2664
2702
  const { valid } = verifyChecksums(extracted.files, extracted.checksums);
2665
2703
  if (!valid) {
2666
- console.log(chalk10.red("Checksum verification failed. Cannot publish."));
2704
+ console.log(chalk11.red("Checksum verification failed. Cannot publish."));
2667
2705
  process.exitCode = 1;
2668
2706
  return;
2669
2707
  }
2670
2708
  if (!extracted.authorSignature) {
2671
- console.log(chalk10.red("No author signature. Sign the package first."));
2709
+ console.log(chalk11.red("No author signature. Sign the package first."));
2672
2710
  process.exitCode = 1;
2673
2711
  return;
2674
2712
  }
@@ -2682,53 +2720,91 @@ async function publishCommand(sspPath) {
2682
2720
  pubKeyPem
2683
2721
  );
2684
2722
  if (!sigValid) {
2685
- console.log(chalk10.red("Signature verification failed. Package may have been tampered with after signing."));
2686
- 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}`));
2687
2725
  process.exitCode = 1;
2688
2726
  return;
2689
2727
  }
2690
- console.log(chalk10.green("\u2713 Signature verified"));
2728
+ console.log(chalk11.green("\u2713 Signature verified"));
2691
2729
  } else {
2692
- console.log(chalk10.yellow("\u26A0 Local public key not found \u2014 skipping local signature check"));
2693
- 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."));
2694
2732
  }
2695
2733
  console.log("Uploading to marketplace...");
2696
- try {
2734
+ async function upload() {
2697
2735
  const formData = new FormData();
2698
2736
  formData.append("file", new Blob([data]), sspPath.split("/").pop());
2699
- const response = await fetch(`${config.marketplace_url}/v1/skills`, {
2737
+ return fetch(`${config.marketplace_url}/v1/skills`, {
2700
2738
  method: "POST",
2701
- headers: {
2702
- Authorization: `Bearer ${config.auth_token}`
2703
- },
2739
+ headers: { Authorization: `Bearer ${config.auth_token}` },
2704
2740
  body: formData
2705
2741
  });
2742
+ }
2743
+ try {
2744
+ let response = await upload();
2706
2745
  if (!response.ok) {
2707
- const errorBody = await response.json();
2708
- console.log(chalk10.red(`Upload failed: ${errorBody.error || response.statusText}`));
2709
- process.exitCode = 1;
2710
- return;
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
+ }
2711
2766
  }
2712
2767
  const result = await response.json();
2713
- console.log(chalk10.green("Published successfully!"));
2768
+ console.log(chalk11.green("Published successfully!"));
2714
2769
  console.log();
2715
- console.log(` ${chalk10.bold("Skill ID:")} ${result.id}`);
2716
- console.log(` ${chalk10.bold("SSP ID:")} ${result.ssp_id}`);
2717
- console.log(` ${chalk10.bold("Version:")} ${result.version}`);
2718
- console.log(` ${chalk10.bold("Scan:")} ${result.scan_passed ? chalk10.green("PASSED") : chalk10.red("FAILED")}`);
2719
- console.log(` ${chalk10.bold("Risk Score:")} ${result.risk_score}/100`);
2770
+ console.log(` ${chalk11.bold("Skill ID:")} ${result.id}`);
2771
+ console.log(` ${chalk11.bold("SSP ID:")} ${result.ssp_id}`);
2772
+ console.log(` ${chalk11.bold("Version:")} ${result.version}`);
2773
+ console.log(` ${chalk11.bold("Scan:")} ${result.scan_passed ? chalk11.green("PASSED") : chalk11.red("FAILED")}`);
2774
+ console.log(` ${chalk11.bold("Risk Score:")} ${result.risk_score}/100`);
2720
2775
  console.log();
2721
- console.log(chalk10.dim(` URL: ${config.marketplace_web_url}/skills/${result.id}`));
2722
- console.log(chalk10.dim(` Install: skillport install ${result.ssp_id}@${result.version}`));
2776
+ console.log(chalk11.dim(` URL: ${config.marketplace_web_url}/skills/${result.id}`));
2777
+ console.log(chalk11.dim(` Install: skillport install ${result.ssp_id}@${result.version}`));
2723
2778
  } catch (error) {
2724
- console.log(chalk10.red(`Upload failed: ${error.message}`));
2779
+ console.log(chalk11.red(`Upload failed: ${error.message}`));
2780
+ process.exitCode = 1;
2781
+ }
2782
+ }
2783
+
2784
+ // src/commands/keys-register.ts
2785
+ init_config();
2786
+ import chalk12 from "chalk";
2787
+ async function keysRegisterCommand() {
2788
+ const config = loadConfig();
2789
+ if (!config.auth_token) {
2790
+ console.log(chalk12.red("Not logged in. Run 'skillport login' first."));
2791
+ process.exitCode = 1;
2792
+ return;
2793
+ }
2794
+ if (!hasKeys()) {
2795
+ console.log(chalk12.red("No signing keys found. Run 'skillport init' first."));
2796
+ process.exitCode = 1;
2797
+ return;
2798
+ }
2799
+ const ok = await registerPublicKey(config);
2800
+ if (!ok) {
2725
2801
  process.exitCode = 1;
2726
2802
  }
2727
2803
  }
2728
2804
 
2729
2805
  // src/index.ts
2730
2806
  var program = new Command();
2731
- program.name("skillport").description("SkillPort \u2014 secure skill distribution for OpenClaw").version("0.1.3");
2807
+ program.name("skillport").description("SkillPort \u2014 secure skill distribution for OpenClaw").version("0.1.6");
2732
2808
  program.command("init").description("Generate Ed25519 key pair for signing").action(initCommand);
2733
2809
  program.command("scan <path>").description("Run security scan on a skill directory or .ssp file").action(scanCommand);
2734
2810
  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);
@@ -2737,6 +2813,8 @@ program.command("verify <ssp>").description("Verify SkillPort package signatures
2737
2813
  program.command("install <target>").description("Install a SkillPort package").option("--accept-risk", "Accept high-risk permissions (shell, critical flags)").option("-y, --yes", "Non-interactive mode (auto-approve, use defaults)").action(installCommand);
2738
2814
  program.command("dry-run <ssp>").description("Run installation diagnostics without installing").action(dryRunCommand);
2739
2815
  program.command("uninstall <id>").description("Uninstall an installed skill").action(uninstallCommand);
2740
- 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)").action(loginCommand);
2816
+ 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);
2741
2817
  program.command("publish <ssp>").description("Publish a SkillPort package to the marketplace").action(publishCommand);
2818
+ var keys = program.command("keys").description("Manage signing keys");
2819
+ keys.command("register").description("Register your public signing key with the marketplace").action(keysRegisterCommand);
2742
2820
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillport/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "description": "SkillPort CLI — secure skill distribution for OpenClaw. Export, scan, sign, and install AI skill packages.",
5
5
  "type": "module",
6
6
  "license": "MIT",