evalify-cli 0.1.3 → 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.
package/dist/index.js CHANGED
@@ -965,8 +965,8 @@ var require_command = __commonJS({
965
965
  "use strict";
966
966
  var EventEmitter = require("events").EventEmitter;
967
967
  var childProcess = require("child_process");
968
- var path4 = require("path");
969
- var fs4 = require("fs");
968
+ var path5 = require("path");
969
+ var fs5 = require("fs");
970
970
  var process3 = require("process");
971
971
  var { Argument: Argument2, humanReadableArgName } = require_argument();
972
972
  var { CommanderError: CommanderError2 } = require_error();
@@ -1898,11 +1898,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1898
1898
  let launchWithNode = false;
1899
1899
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1900
1900
  function findFile(baseDir, baseName) {
1901
- const localBin = path4.resolve(baseDir, baseName);
1902
- if (fs4.existsSync(localBin)) return localBin;
1903
- if (sourceExt.includes(path4.extname(baseName))) return void 0;
1901
+ const localBin = path5.resolve(baseDir, baseName);
1902
+ if (fs5.existsSync(localBin)) return localBin;
1903
+ if (sourceExt.includes(path5.extname(baseName))) return void 0;
1904
1904
  const foundExt = sourceExt.find(
1905
- (ext) => fs4.existsSync(`${localBin}${ext}`)
1905
+ (ext) => fs5.existsSync(`${localBin}${ext}`)
1906
1906
  );
1907
1907
  if (foundExt) return `${localBin}${foundExt}`;
1908
1908
  return void 0;
@@ -1914,21 +1914,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1914
1914
  if (this._scriptPath) {
1915
1915
  let resolvedScriptPath;
1916
1916
  try {
1917
- resolvedScriptPath = fs4.realpathSync(this._scriptPath);
1917
+ resolvedScriptPath = fs5.realpathSync(this._scriptPath);
1918
1918
  } catch (err) {
1919
1919
  resolvedScriptPath = this._scriptPath;
1920
1920
  }
1921
- executableDir = path4.resolve(
1922
- path4.dirname(resolvedScriptPath),
1921
+ executableDir = path5.resolve(
1922
+ path5.dirname(resolvedScriptPath),
1923
1923
  executableDir
1924
1924
  );
1925
1925
  }
1926
1926
  if (executableDir) {
1927
1927
  let localFile = findFile(executableDir, executableFile);
1928
1928
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1929
- const legacyName = path4.basename(
1929
+ const legacyName = path5.basename(
1930
1930
  this._scriptPath,
1931
- path4.extname(this._scriptPath)
1931
+ path5.extname(this._scriptPath)
1932
1932
  );
1933
1933
  if (legacyName !== this._name) {
1934
1934
  localFile = findFile(
@@ -1939,7 +1939,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1939
1939
  }
1940
1940
  executableFile = localFile || executableFile;
1941
1941
  }
1942
- launchWithNode = sourceExt.includes(path4.extname(executableFile));
1942
+ launchWithNode = sourceExt.includes(path5.extname(executableFile));
1943
1943
  let proc;
1944
1944
  if (process3.platform !== "win32") {
1945
1945
  if (launchWithNode) {
@@ -2779,7 +2779,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2779
2779
  * @return {Command}
2780
2780
  */
2781
2781
  nameFromFilename(filename) {
2782
- this._name = path4.basename(filename, path4.extname(filename));
2782
+ this._name = path5.basename(filename, path5.extname(filename));
2783
2783
  return this;
2784
2784
  }
2785
2785
  /**
@@ -2793,9 +2793,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2793
2793
  * @param {string} [path]
2794
2794
  * @return {(string|null|Command)}
2795
2795
  */
2796
- executableDir(path5) {
2797
- if (path5 === void 0) return this._executableDir;
2798
- this._executableDir = path5;
2796
+ executableDir(path6) {
2797
+ if (path6 === void 0) return this._executableDir;
2798
+ this._executableDir = path6;
2799
2799
  return this;
2800
2800
  }
2801
2801
  /**
@@ -5613,7 +5613,7 @@ var require_dist = __commonJS({
5613
5613
  });
5614
5614
  };
5615
5615
  }
5616
- var prompts2 = require_prompts();
5616
+ var prompts3 = require_prompts();
5617
5617
  var passOn = ["suggest", "format", "onState", "validate", "onRender", "type"];
5618
5618
  var noop = () => {
5619
5619
  };
@@ -5664,7 +5664,7 @@ var require_dist = __commonJS({
5664
5664
  var _question2 = question;
5665
5665
  name = _question2.name;
5666
5666
  type = _question2.type;
5667
- if (prompts2[type] === void 0) {
5667
+ if (prompts3[type] === void 0) {
5668
5668
  throw new Error(`prompt type (${type}) is not defined`);
5669
5669
  }
5670
5670
  if (override2[question.name] !== void 0) {
@@ -5675,7 +5675,7 @@ var require_dist = __commonJS({
5675
5675
  }
5676
5676
  }
5677
5677
  try {
5678
- answer = prompt._injected ? getInjectedAnswer(prompt._injected, question.initial) : yield prompts2[type](question);
5678
+ answer = prompt._injected ? getInjectedAnswer(prompt._injected, question.initial) : yield prompts3[type](question);
5679
5679
  answers[name] = answer = yield getFormattedAnswer(question, answer, true);
5680
5680
  quit = yield onSubmit(question, answer, answers);
5681
5681
  } catch (err) {
@@ -5707,7 +5707,7 @@ var require_dist = __commonJS({
5707
5707
  }
5708
5708
  module2.exports = Object.assign(prompt, {
5709
5709
  prompt,
5710
- prompts: prompts2,
5710
+ prompts: prompts3,
5711
5711
  inject,
5712
5712
  override
5713
5713
  });
@@ -7794,7 +7794,7 @@ var require_prompts2 = __commonJS({
7794
7794
  var require_lib = __commonJS({
7795
7795
  "../node_modules/.pnpm/prompts@2.4.2/node_modules/prompts/lib/index.js"(exports2, module2) {
7796
7796
  "use strict";
7797
- var prompts2 = require_prompts2();
7797
+ var prompts3 = require_prompts2();
7798
7798
  var passOn = ["suggest", "format", "onState", "validate", "onRender", "type"];
7799
7799
  var noop = () => {
7800
7800
  };
@@ -7826,7 +7826,7 @@ var require_lib = __commonJS({
7826
7826
  throw new Error("prompt message is required");
7827
7827
  }
7828
7828
  ({ name, type } = question);
7829
- if (prompts2[type] === void 0) {
7829
+ if (prompts3[type] === void 0) {
7830
7830
  throw new Error(`prompt type (${type}) is not defined`);
7831
7831
  }
7832
7832
  if (override2[question.name] !== void 0) {
@@ -7837,7 +7837,7 @@ var require_lib = __commonJS({
7837
7837
  }
7838
7838
  }
7839
7839
  try {
7840
- answer = prompt._injected ? getInjectedAnswer(prompt._injected, question.initial) : await prompts2[type](question);
7840
+ answer = prompt._injected ? getInjectedAnswer(prompt._injected, question.initial) : await prompts3[type](question);
7841
7841
  answers[name] = answer = await getFormattedAnswer(question, answer, true);
7842
7842
  quit = await onSubmit(question, answer, answers);
7843
7843
  } catch (err) {
@@ -7860,7 +7860,7 @@ var require_lib = __commonJS({
7860
7860
  function override(answers) {
7861
7861
  prompt._override = Object.assign({}, answers);
7862
7862
  }
7863
- module2.exports = Object.assign(prompt, { prompt, prompts: prompts2, inject, override });
7863
+ module2.exports = Object.assign(prompt, { prompt, prompts: prompts3, inject, override });
7864
7864
  }
7865
7865
  });
7866
7866
 
@@ -8394,10 +8394,10 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
8394
8394
  var source_default = chalk;
8395
8395
 
8396
8396
  // src/format.ts
8397
- var VERSION = "0.1.0";
8397
+ var VERSION = "0.1.4";
8398
8398
  function header() {
8399
8399
  console.log(source_default.bold.cyan(`
8400
- evalify`) + source_default.dim(` v${VERSION}
8400
+ evalify-cli`) + source_default.dim(` v${VERSION}
8401
8401
  `));
8402
8402
  }
8403
8403
  function success(msg) {
@@ -8431,7 +8431,7 @@ var import_node_path = __toESM(require("path"));
8431
8431
  var import_node_os2 = __toESM(require("os"));
8432
8432
  var import_promises = __toESM(require("fs/promises"));
8433
8433
  var import_prompts = __toESM(require_prompts3());
8434
- var REGISTRY_URL = "https://evalify.sh/api/registry";
8434
+ var REGISTRY_URL = "https://www.evalify.sh/api/registry";
8435
8435
  async function detectSkills(skillsDir) {
8436
8436
  try {
8437
8437
  const entries = await import_promises.default.readdir(skillsDir, { withFileTypes: true });
@@ -8610,7 +8610,7 @@ async function pull(slug) {
8610
8610
  console.log();
8611
8611
  dim(`Author: ${pack.author}`);
8612
8612
  dim(`Domain: ${pack.domain}`);
8613
- dim(`To validate: evalify validate ${targetDir}`);
8613
+ dim(`To validate: evalify-cli validate ${targetDir}`);
8614
8614
  } catch (err) {
8615
8615
  error(`Failed to write file: ${err.message}`);
8616
8616
  }
@@ -8618,8 +8618,9 @@ async function pull(slug) {
8618
8618
  }
8619
8619
 
8620
8620
  // src/commands/publish.ts
8621
- var import_node_path2 = __toESM(require("path"));
8622
- var import_promises2 = __toESM(require("fs/promises"));
8621
+ var import_node_path3 = __toESM(require("path"));
8622
+ var import_promises3 = __toESM(require("fs/promises"));
8623
+ var import_prompts2 = __toESM(require_prompts3());
8623
8624
 
8624
8625
  // ../packages/frameworks/dist/anthropic-skillcreator-v2.js
8625
8626
  function extractItems(parsed) {
@@ -8810,17 +8811,154 @@ function validateEvalsJson(content) {
8810
8811
  return result;
8811
8812
  }
8812
8813
 
8814
+ // src/commands/login.ts
8815
+ var import_node_http = __toESM(require("http"));
8816
+ var import_node_crypto = __toESM(require("crypto"));
8817
+ var import_node_child_process = require("child_process");
8818
+ var import_node_path2 = __toESM(require("path"));
8819
+ var import_node_os3 = __toESM(require("os"));
8820
+ var import_promises2 = __toESM(require("fs/promises"));
8821
+ var AUTH_FILE = import_node_path2.default.join(import_node_os3.default.homedir(), ".evalify", "auth.json");
8822
+ var REGISTRY_BASE = "https://www.evalify.sh";
8823
+ async function readAuth() {
8824
+ try {
8825
+ const content = await import_promises2.default.readFile(AUTH_FILE, "utf-8");
8826
+ return JSON.parse(content);
8827
+ } catch {
8828
+ return null;
8829
+ }
8830
+ }
8831
+ async function logout() {
8832
+ header();
8833
+ try {
8834
+ await import_promises2.default.rm(AUTH_FILE);
8835
+ success("Logged out");
8836
+ } catch {
8837
+ error("Not logged in");
8838
+ }
8839
+ console.log();
8840
+ }
8841
+ function openBrowser(url) {
8842
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
8843
+ (0, import_node_child_process.exec)(`${cmd} "${url}"`);
8844
+ }
8845
+ async function getValidToken() {
8846
+ const auth = await readAuth();
8847
+ if (!auth) return null;
8848
+ try {
8849
+ const payload = JSON.parse(Buffer.from(auth.access_token.split(".")[1], "base64url").toString());
8850
+ const expiresAt = payload.exp * 1e3;
8851
+ if (Date.now() < expiresAt - 6e4) return auth.access_token;
8852
+ } catch {
8853
+ return auth.access_token;
8854
+ }
8855
+ if (!auth.refresh_token) return null;
8856
+ try {
8857
+ const res = await fetch(`${REGISTRY_BASE}/api/auth/refresh`, {
8858
+ method: "POST",
8859
+ headers: { "Content-Type": "application/json" },
8860
+ body: JSON.stringify({ refresh_token: auth.refresh_token })
8861
+ });
8862
+ if (!res.ok) return null;
8863
+ const data = await res.json();
8864
+ const updated = {
8865
+ access_token: data.access_token,
8866
+ refresh_token: data.refresh_token ?? auth.refresh_token,
8867
+ handle: auth.handle
8868
+ };
8869
+ await import_promises2.default.writeFile(AUTH_FILE, JSON.stringify(updated, null, 2));
8870
+ return updated.access_token;
8871
+ } catch {
8872
+ return null;
8873
+ }
8874
+ }
8875
+ async function login() {
8876
+ header();
8877
+ info("Logging in to Evalify...");
8878
+ console.log();
8879
+ const tokens = await waitForToken();
8880
+ if (!tokens) return;
8881
+ let handle = "";
8882
+ try {
8883
+ const res = await fetch(`${REGISTRY_BASE}/api/auth/me`, {
8884
+ headers: { Authorization: `Bearer ${tokens.access_token}` }
8885
+ });
8886
+ if (res.ok) {
8887
+ const data = await res.json();
8888
+ handle = data.handle ?? "";
8889
+ }
8890
+ } catch {
8891
+ }
8892
+ const authDir = import_node_path2.default.dirname(AUTH_FILE);
8893
+ await import_promises2.default.mkdir(authDir, { recursive: true });
8894
+ await import_promises2.default.writeFile(
8895
+ AUTH_FILE,
8896
+ JSON.stringify({ access_token: tokens.access_token, refresh_token: tokens.refresh_token, handle }, null, 2)
8897
+ );
8898
+ console.log();
8899
+ success(`Logged in${handle ? ` as ${handle}` : ""}`);
8900
+ dim(`Token saved to ${AUTH_FILE}`);
8901
+ console.log();
8902
+ }
8903
+ function waitForToken() {
8904
+ return new Promise((resolve) => {
8905
+ const state = import_node_crypto.default.randomBytes(16).toString("hex");
8906
+ const server = import_node_http.default.createServer((req, res) => {
8907
+ const url = new URL(req.url ?? "/", "http://localhost");
8908
+ if (url.pathname !== "/callback") {
8909
+ res.writeHead(404);
8910
+ res.end();
8911
+ return;
8912
+ }
8913
+ const returnedState = url.searchParams.get("state");
8914
+ const token = url.searchParams.get("token");
8915
+ const refreshToken = url.searchParams.get("refresh_token");
8916
+ const ok = returnedState === state && !!token;
8917
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
8918
+ res.end(`<!doctype html><html><body style="font-family:sans-serif;padding:2rem;background:#0a0a0a;color:#e4e4e7">
8919
+ ${ok ? `<h2 style="color:#22c55e">&#10003; Authentication successful</h2><p style="color:#71717a">You can close this tab and return to your terminal.</p>` : `<h2 style="color:#ef4444">&#10007; Authentication failed</h2><p style="color:#71717a">Invalid state. Please try again.</p>`}
8920
+ </body></html>`);
8921
+ req.socket.destroy();
8922
+ server.close();
8923
+ if (!ok) {
8924
+ error(returnedState !== state ? "State mismatch \u2014 possible CSRF" : "No token received");
8925
+ resolve(null);
8926
+ return;
8927
+ }
8928
+ resolve({ access_token: token, refresh_token: refreshToken ?? "" });
8929
+ });
8930
+ server.unref();
8931
+ server.listen(0, "127.0.0.1", () => {
8932
+ const port = server.address().port;
8933
+ const authUrl = `${REGISTRY_BASE}/auth/cli?port=${port}&state=${state}`;
8934
+ dim(`Opening ${authUrl}`);
8935
+ dim("Waiting for authentication...");
8936
+ openBrowser(authUrl);
8937
+ });
8938
+ server.on("error", (err) => {
8939
+ error(`Local server error: ${err.message}`);
8940
+ resolve(null);
8941
+ });
8942
+ setTimeout(() => {
8943
+ server.close();
8944
+ error("Authentication timed out");
8945
+ resolve(null);
8946
+ }, 5 * 60 * 1e3);
8947
+ });
8948
+ }
8949
+
8813
8950
  // src/commands/publish.ts
8951
+ var REGISTRY_URL2 = "https://www.evalify.sh/api/publish";
8814
8952
  async function findEvalsFile(targetPath) {
8815
- const stat = await import_promises2.default.stat(targetPath);
8953
+ const stat = await import_promises3.default.stat(targetPath);
8816
8954
  if (stat.isFile()) return targetPath;
8817
8955
  const candidates = [
8818
- import_node_path2.default.join(targetPath, "evals", "evals.json"),
8819
- import_node_path2.default.join(targetPath, "evals.json")
8956
+ import_node_path3.default.join(targetPath, "evals", "evals.json"),
8957
+ import_node_path3.default.join(targetPath, "evals.json")
8820
8958
  ];
8821
8959
  for (const candidate of candidates) {
8822
8960
  try {
8823
- await import_promises2.default.access(candidate);
8961
+ await import_promises3.default.access(candidate);
8824
8962
  return candidate;
8825
8963
  } catch {
8826
8964
  }
@@ -8829,15 +8967,43 @@ async function findEvalsFile(targetPath) {
8829
8967
  }
8830
8968
  async function publish(targetPath) {
8831
8969
  header();
8832
- const resolvedPath = import_node_path2.default.resolve(process.cwd(), targetPath || ".");
8970
+ let auth = await readAuth();
8971
+ if (!auth) {
8972
+ error("Not logged in");
8973
+ console.log();
8974
+ const { action } = await (0, import_prompts2.default)({
8975
+ type: "select",
8976
+ name: "action",
8977
+ message: "What would you like to do?",
8978
+ choices: [
8979
+ { title: "Login now", value: "login" },
8980
+ { title: "Later", value: "later" }
8981
+ ],
8982
+ initial: 0
8983
+ });
8984
+ if (!action || action === "later") {
8985
+ console.log();
8986
+ dim("Run: evalify-cli login when ready.");
8987
+ console.log();
8988
+ return;
8989
+ }
8990
+ await login();
8991
+ auth = await readAuth();
8992
+ if (!auth) {
8993
+ console.log();
8994
+ return;
8995
+ }
8996
+ }
8997
+ const resolvedPath = import_node_path3.default.resolve(process.cwd(), targetPath || ".");
8833
8998
  try {
8834
- await import_promises2.default.access(resolvedPath);
8999
+ await import_promises3.default.access(resolvedPath);
8835
9000
  } catch {
8836
- error(`Path not found: ${targetPath || "."}`);
9001
+ error(`Path not found: ${resolvedPath}`);
9002
+ dim("Pass a path to a folder containing evals.json, or run from that folder.");
8837
9003
  console.log();
8838
9004
  return;
8839
9005
  }
8840
- info("Publishing eval criteria to registry...");
9006
+ info(`Publishing from ${import_node_path3.default.relative(process.cwd(), resolvedPath) || "."}`);
8841
9007
  console.log();
8842
9008
  const filePath = await findEvalsFile(resolvedPath);
8843
9009
  if (!filePath) {
@@ -8848,8 +9014,8 @@ async function publish(targetPath) {
8848
9014
  console.log();
8849
9015
  return;
8850
9016
  }
8851
- success(`Found ${import_node_path2.default.relative(process.cwd(), filePath)}`);
8852
- const content = await import_promises2.default.readFile(filePath, "utf-8");
9017
+ success(`Found ${import_node_path3.default.relative(process.cwd(), filePath)}`);
9018
+ const content = await import_promises3.default.readFile(filePath, "utf-8");
8853
9019
  const result = validateEvalsJson(content);
8854
9020
  if (!result.valid) {
8855
9021
  console.log();
@@ -8863,28 +9029,71 @@ async function publish(targetPath) {
8863
9029
  for (const w of result.warnings) {
8864
9030
  warn(w);
8865
9031
  }
9032
+ const parsed = JSON.parse(content);
9033
+ const displayName = parsed.displayName ?? parsed.skill_name ?? parsed.name ?? "";
9034
+ const slug = parsed.slug ?? displayName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
8866
9035
  console.log();
8867
9036
  console.log(source_default.bold(" Publish summary:"));
8868
9037
  console.log();
8869
- if (result.summary["skill_name"]) {
8870
- dim(`Skill: ${result.summary["skill_name"]}`);
8871
- }
8872
- if (result.summary["name"]) {
8873
- dim(`Name: ${result.summary["name"]}`);
8874
- }
8875
- if (result.summary["version"]) {
8876
- dim(`Version: ${result.summary["version"]}`);
8877
- }
8878
- if (result.summary["description"]) {
8879
- dim(`Description: ${result.summary["description"]}`);
8880
- }
9038
+ dim(`Name: ${displayName || "(unnamed)"}`);
9039
+ dim(`Slug: ${slug || "(auto)"}`);
9040
+ dim(`Version: ${parsed.version ?? "1.0.0"}`);
9041
+ dim(`Domain: ${parsed.domain ?? "general"}`);
8881
9042
  dim(`Format: ${getFramework(result.format)?.meta.name ?? result.format}`);
8882
9043
  dim(`Eval count: ${result.evalCount}`);
8883
- dim(`File: ${import_node_path2.default.relative(process.cwd(), filePath)}`);
8884
- console.log();
8885
- warn("Dry run \u2014 publishing is not yet connected to the registry");
8886
- success("File is valid and ready to publish");
9044
+ dim(`Author: ${auth.handle}`);
8887
9045
  console.log();
9046
+ const { confirm } = await (0, import_prompts2.default)({
9047
+ type: "confirm",
9048
+ name: "confirm",
9049
+ message: "Publish to evalify.sh?",
9050
+ initial: true
9051
+ });
9052
+ if (!confirm) {
9053
+ console.log();
9054
+ dim("Cancelled.");
9055
+ console.log();
9056
+ return;
9057
+ }
9058
+ info("Uploading...");
9059
+ try {
9060
+ const token = await getValidToken();
9061
+ if (!token) {
9062
+ error("Session expired \u2014 run: evalify-cli login");
9063
+ return;
9064
+ }
9065
+ const res = await fetch(REGISTRY_URL2, {
9066
+ method: "POST",
9067
+ headers: {
9068
+ "Content-Type": "application/json",
9069
+ Authorization: `Bearer ${token}`
9070
+ },
9071
+ body: JSON.stringify({
9072
+ slug,
9073
+ displayName,
9074
+ domain: parsed.domain ?? "general",
9075
+ version: parsed.version ?? "1.0.0",
9076
+ tags: parsed.tags ?? [],
9077
+ description: parsed.description ?? "",
9078
+ evals: parsed.evals ?? []
9079
+ })
9080
+ });
9081
+ const data = await res.json();
9082
+ if (!res.ok) {
9083
+ console.log();
9084
+ error(data.error ?? `Server error ${res.status}`);
9085
+ console.log();
9086
+ return;
9087
+ }
9088
+ console.log();
9089
+ success(`Published ${displayName || slug}`);
9090
+ dim(`View at: https://evalify.sh/criteria/${data.slug}`);
9091
+ console.log();
9092
+ } catch (err) {
9093
+ console.log();
9094
+ error(`Failed to publish: ${err.message}`);
9095
+ console.log();
9096
+ }
8888
9097
  }
8889
9098
 
8890
9099
  // src/commands/search.ts
@@ -8902,13 +9111,13 @@ async function search(query) {
8902
9111
  table(results);
8903
9112
  console.log();
8904
9113
  dim(`Showing placeholder results \u2014 registry search not yet connected`);
8905
- dim(`Use: evalify pull <slug> to download criteria`);
9114
+ dim(`Use: evalify-cli pull <slug> to download criteria`);
8906
9115
  console.log();
8907
9116
  }
8908
9117
 
8909
9118
  // src/commands/validate.ts
8910
- var import_node_path3 = __toESM(require("path"));
8911
- var import_promises3 = __toESM(require("fs/promises"));
9119
+ var import_node_path4 = __toESM(require("path"));
9120
+ var import_promises4 = __toESM(require("fs/promises"));
8912
9121
  var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([
8913
9122
  ".json",
8914
9123
  ".md",
@@ -8928,17 +9137,17 @@ var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([
8928
9137
  ".toml"
8929
9138
  ]);
8930
9139
  async function findEvalsJson(targetPath) {
8931
- const stat = await import_promises3.default.stat(targetPath);
9140
+ const stat = await import_promises4.default.stat(targetPath);
8932
9141
  if (stat.isFile()) {
8933
- return { evalsPath: targetPath, rootDir: import_node_path3.default.dirname(targetPath) };
9142
+ return { evalsPath: targetPath, rootDir: import_node_path4.default.dirname(targetPath) };
8934
9143
  }
8935
9144
  const candidates = [
8936
- import_node_path3.default.join(targetPath, "evals", "evals.json"),
8937
- import_node_path3.default.join(targetPath, "evals.json")
9145
+ import_node_path4.default.join(targetPath, "evals", "evals.json"),
9146
+ import_node_path4.default.join(targetPath, "evals.json")
8938
9147
  ];
8939
9148
  for (const candidate of candidates) {
8940
9149
  try {
8941
- await import_promises3.default.access(candidate);
9150
+ await import_promises4.default.access(candidate);
8942
9151
  return { evalsPath: candidate, rootDir: targetPath };
8943
9152
  } catch {
8944
9153
  }
@@ -8947,19 +9156,19 @@ async function findEvalsJson(targetPath) {
8947
9156
  }
8948
9157
  async function scanCompanionFiles(rootDir, evalsPath, maxDepth = 2) {
8949
9158
  const companions = [];
8950
- const evalsRelative = import_node_path3.default.relative(rootDir, evalsPath);
9159
+ const evalsRelative = import_node_path4.default.relative(rootDir, evalsPath);
8951
9160
  async function walk(dir, depth) {
8952
9161
  if (depth > maxDepth) return;
8953
- const entries = await import_promises3.default.readdir(dir, { withFileTypes: true });
9162
+ const entries = await import_promises4.default.readdir(dir, { withFileTypes: true });
8954
9163
  for (const entry of entries) {
8955
9164
  if (entry.name.startsWith(".")) continue;
8956
- const fullPath = import_node_path3.default.join(dir, entry.name);
8957
- const relativePath = import_node_path3.default.relative(rootDir, fullPath);
9165
+ const fullPath = import_node_path4.default.join(dir, entry.name);
9166
+ const relativePath = import_node_path4.default.relative(rootDir, fullPath);
8958
9167
  if (entry.isDirectory()) {
8959
9168
  await walk(fullPath, depth + 1);
8960
9169
  } else if (entry.isFile()) {
8961
9170
  if (relativePath === evalsRelative) continue;
8962
- const ext = import_node_path3.default.extname(entry.name).toLowerCase();
9171
+ const ext = import_node_path4.default.extname(entry.name).toLowerCase();
8963
9172
  if (ALLOWED_EXTENSIONS.has(ext)) {
8964
9173
  companions.push(relativePath);
8965
9174
  }
@@ -8984,20 +9193,20 @@ function extractFileRefs(content) {
8984
9193
  }
8985
9194
  async function validate(targetPath) {
8986
9195
  header();
8987
- const resolvedPath = import_node_path3.default.resolve(process.cwd(), targetPath || ".");
9196
+ const resolvedPath = import_node_path4.default.resolve(process.cwd(), targetPath || ".");
8988
9197
  try {
8989
- await import_promises3.default.access(resolvedPath);
9198
+ await import_promises4.default.access(resolvedPath);
8990
9199
  } catch {
8991
9200
  error(`Path not found: ${targetPath || "."}`);
8992
9201
  console.log();
8993
9202
  return;
8994
9203
  }
8995
- const stat = await import_promises3.default.stat(resolvedPath);
9204
+ const stat = await import_promises4.default.stat(resolvedPath);
8996
9205
  const isFolder = stat.isDirectory();
8997
9206
  if (isFolder) {
8998
- info(`Scanning folder: ${import_node_path3.default.basename(resolvedPath)}/`);
9207
+ info(`Scanning folder: ${import_node_path4.default.basename(resolvedPath)}/`);
8999
9208
  } else {
9000
- info(`Validating: ${import_node_path3.default.basename(resolvedPath)}`);
9209
+ info(`Validating: ${import_node_path4.default.basename(resolvedPath)}`);
9001
9210
  }
9002
9211
  const found = await findEvalsJson(resolvedPath);
9003
9212
  if (!found) {
@@ -9010,7 +9219,7 @@ async function validate(targetPath) {
9010
9219
  return;
9011
9220
  }
9012
9221
  const { evalsPath, rootDir } = found;
9013
- const relEvalsPath = import_node_path3.default.relative(process.cwd(), evalsPath);
9222
+ const relEvalsPath = import_node_path4.default.relative(process.cwd(), evalsPath);
9014
9223
  success(`Found ${relEvalsPath}`);
9015
9224
  const companions = isFolder ? await scanCompanionFiles(rootDir, evalsPath) : [];
9016
9225
  if (companions.length > 0) {
@@ -9019,7 +9228,7 @@ async function validate(targetPath) {
9019
9228
  dim(c);
9020
9229
  }
9021
9230
  }
9022
- const content = await import_promises3.default.readFile(evalsPath, "utf-8");
9231
+ const content = await import_promises4.default.readFile(evalsPath, "utf-8");
9023
9232
  const result = validateEvalsJson(content);
9024
9233
  const fileRefs = extractFileRefs(content);
9025
9234
  if (fileRefs.length > 0) {
@@ -9078,7 +9287,13 @@ async function validate(targetPath) {
9078
9287
 
9079
9288
  // src/index.ts
9080
9289
  var program2 = new Command();
9081
- program2.name("evalify").description("CLI tool for the Evalify eval criteria registry").version(VERSION);
9290
+ program2.name("evalify-cli").description("CLI tool for the Evalify eval criteria registry").version(VERSION);
9291
+ program2.command("login").description("Authenticate with the Evalify registry").action(async () => {
9292
+ await login();
9293
+ });
9294
+ program2.command("logout").description("Remove saved credentials").action(async () => {
9295
+ await logout();
9296
+ });
9082
9297
  program2.command("pull <slug>").description("Download eval criteria from the registry").action(async (slug) => {
9083
9298
  await pull(slug);
9084
9299
  });
@@ -9091,4 +9306,4 @@ program2.command("search <query>").description("Search the registry for eval cri
9091
9306
  program2.command("validate [path]").description("Validate evals.json from a file or skill folder").action(async (targetPath) => {
9092
9307
  await validate(targetPath);
9093
9308
  });
9094
- program2.parse();
9309
+ program2.parseAsync().then(() => process.exit(0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evalify-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "CLI tool for the Evalify eval criteria registry",
5
5
  "homepage": "https://evalify.sh",
6
6
  "repository": "https://github.com/AppVerse-cc/evalify",
@@ -0,0 +1,181 @@
1
+ import http from "node:http";
2
+ import crypto from "node:crypto";
3
+ import { exec } from "node:child_process";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import fs from "node:fs/promises";
7
+ import { header, success, info, dim, error } from "../format.js";
8
+
9
+ const AUTH_FILE = path.join(os.homedir(), ".evalify", "auth.json");
10
+ const REGISTRY_BASE = "https://www.evalify.sh";
11
+
12
+ export interface AuthData {
13
+ access_token: string;
14
+ refresh_token: string;
15
+ handle: string;
16
+ }
17
+
18
+ export async function readAuth(): Promise<AuthData | null> {
19
+ try {
20
+ const content = await fs.readFile(AUTH_FILE, "utf-8");
21
+ return JSON.parse(content) as AuthData;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ export async function logout(): Promise<void> {
28
+ header();
29
+ try {
30
+ await fs.rm(AUTH_FILE);
31
+ success("Logged out");
32
+ } catch {
33
+ error("Not logged in");
34
+ }
35
+ console.log();
36
+ }
37
+
38
+ function openBrowser(url: string): void {
39
+ const cmd =
40
+ process.platform === "darwin"
41
+ ? "open"
42
+ : process.platform === "win32"
43
+ ? "start"
44
+ : "xdg-open";
45
+ exec(`${cmd} "${url}"`);
46
+ }
47
+
48
+ /** Returns a valid (non-expired) access token, refreshing if needed. */
49
+ export async function getValidToken(): Promise<string | null> {
50
+ const auth = await readAuth();
51
+ if (!auth) return null;
52
+
53
+ // Decode JWT expiry without a library
54
+ try {
55
+ const payload = JSON.parse(Buffer.from(auth.access_token.split(".")[1], "base64url").toString());
56
+ const expiresAt: number = payload.exp * 1000;
57
+ if (Date.now() < expiresAt - 60_000) return auth.access_token; // still valid
58
+ } catch {
59
+ return auth.access_token; // can't decode, try anyway
60
+ }
61
+
62
+ if (!auth.refresh_token) return null;
63
+
64
+ // Refresh via evalify.sh refresh endpoint
65
+ try {
66
+ const res = await fetch(`${REGISTRY_BASE}/api/auth/refresh`, {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/json" },
69
+ body: JSON.stringify({ refresh_token: auth.refresh_token }),
70
+ });
71
+ if (!res.ok) return null;
72
+ const data = await res.json();
73
+ const updated: AuthData = {
74
+ access_token: data.access_token,
75
+ refresh_token: data.refresh_token ?? auth.refresh_token,
76
+ handle: auth.handle,
77
+ };
78
+ await fs.writeFile(AUTH_FILE, JSON.stringify(updated, null, 2));
79
+ return updated.access_token;
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ export async function login(): Promise<void> {
86
+ header();
87
+ info("Logging in to Evalify...");
88
+ console.log();
89
+
90
+ const tokens = await waitForToken();
91
+ if (!tokens) return;
92
+
93
+ // Verify token and fetch handle
94
+ let handle = "";
95
+ try {
96
+ const res = await fetch(`${REGISTRY_BASE}/api/auth/me`, {
97
+ headers: { Authorization: `Bearer ${tokens.access_token}` },
98
+ });
99
+ if (res.ok) {
100
+ const data = await res.json();
101
+ handle = data.handle ?? "";
102
+ }
103
+ } catch {
104
+ // non-critical — save token anyway
105
+ }
106
+
107
+ // Save auth
108
+ const authDir = path.dirname(AUTH_FILE);
109
+ await fs.mkdir(authDir, { recursive: true });
110
+ await fs.writeFile(
111
+ AUTH_FILE,
112
+ JSON.stringify({ access_token: tokens.access_token, refresh_token: tokens.refresh_token, handle }, null, 2)
113
+ );
114
+
115
+ console.log();
116
+ success(`Logged in${handle ? ` as ${handle}` : ""}`);
117
+ dim(`Token saved to ${AUTH_FILE}`);
118
+ console.log();
119
+ }
120
+
121
+ function waitForToken(): Promise<{ access_token: string; refresh_token: string } | null> {
122
+ return new Promise((resolve) => {
123
+ const state = crypto.randomBytes(16).toString("hex");
124
+
125
+ const server = http.createServer((req, res) => {
126
+ const url = new URL(req.url ?? "/", "http://localhost");
127
+
128
+ if (url.pathname !== "/callback") {
129
+ res.writeHead(404);
130
+ res.end();
131
+ return;
132
+ }
133
+
134
+ const returnedState = url.searchParams.get("state");
135
+ const token = url.searchParams.get("token");
136
+ const refreshToken = url.searchParams.get("refresh_token");
137
+
138
+ const ok = returnedState === state && !!token;
139
+
140
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
141
+ res.end(`<!doctype html><html><body style="font-family:sans-serif;padding:2rem;background:#0a0a0a;color:#e4e4e7">
142
+ ${ok
143
+ ? `<h2 style="color:#22c55e">&#10003; Authentication successful</h2><p style="color:#71717a">You can close this tab and return to your terminal.</p>`
144
+ : `<h2 style="color:#ef4444">&#10007; Authentication failed</h2><p style="color:#71717a">Invalid state. Please try again.</p>`
145
+ }
146
+ </body></html>`);
147
+
148
+ req.socket.destroy();
149
+ server.close();
150
+
151
+ if (!ok) {
152
+ error(returnedState !== state ? "State mismatch — possible CSRF" : "No token received");
153
+ resolve(null);
154
+ return;
155
+ }
156
+
157
+ resolve({ access_token: token!, refresh_token: refreshToken ?? "" });
158
+ });
159
+
160
+ server.unref();
161
+ server.listen(0, "127.0.0.1", () => {
162
+ const port = (server.address() as { port: number }).port;
163
+ const authUrl = `${REGISTRY_BASE}/auth/cli?port=${port}&state=${state}`;
164
+ dim(`Opening ${authUrl}`);
165
+ dim("Waiting for authentication...");
166
+ openBrowser(authUrl);
167
+ });
168
+
169
+ server.on("error", (err) => {
170
+ error(`Local server error: ${err.message}`);
171
+ resolve(null);
172
+ });
173
+
174
+ // Timeout after 5 minutes
175
+ setTimeout(() => {
176
+ server.close();
177
+ error("Authentication timed out");
178
+ resolve(null);
179
+ }, 5 * 60 * 1000);
180
+ });
181
+ }
@@ -1,9 +1,13 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs/promises";
3
3
  import chalk from "chalk";
4
+ import prompts from "prompts";
4
5
  import { getFramework } from "@evalify/frameworks";
5
6
  import { header, success, info, dim, error, warn } from "../format.js";
6
7
  import { validateEvalsJson } from "../validator.js";
8
+ import { readAuth, getValidToken, login } from "./login.js";
9
+
10
+ const REGISTRY_URL = "https://www.evalify.sh/api/publish";
7
11
 
8
12
  async function findEvalsFile(targetPath: string): Promise<string | null> {
9
13
  const stat = await fs.stat(targetPath);
@@ -30,17 +34,47 @@ async function findEvalsFile(targetPath: string): Promise<string | null> {
30
34
  export async function publish(targetPath?: string): Promise<void> {
31
35
  header();
32
36
 
37
+ // Check auth
38
+ let auth = await readAuth();
39
+ if (!auth) {
40
+ error("Not logged in");
41
+ console.log();
42
+ const { action } = await prompts({
43
+ type: "select",
44
+ name: "action",
45
+ message: "What would you like to do?",
46
+ choices: [
47
+ { title: "Login now", value: "login" },
48
+ { title: "Later", value: "later" },
49
+ ],
50
+ initial: 0,
51
+ });
52
+ if (!action || action === "later") {
53
+ console.log();
54
+ dim("Run: evalify-cli login when ready.");
55
+ console.log();
56
+ return;
57
+ }
58
+ await login();
59
+ auth = await readAuth();
60
+ if (!auth) {
61
+ console.log();
62
+ return;
63
+ }
64
+ }
65
+
33
66
  const resolvedPath = path.resolve(process.cwd(), targetPath || ".");
34
67
 
35
68
  try {
36
69
  await fs.access(resolvedPath);
37
70
  } catch {
38
- error(`Path not found: ${targetPath || "."}`);
71
+ error(`Path not found: ${resolvedPath}`);
72
+ dim("Pass a path to a folder containing evals.json, or run from that folder.");
39
73
  console.log();
40
74
  return;
41
75
  }
42
76
 
43
- info("Publishing eval criteria to registry...");
77
+ info(`Publishing from ${path.relative(process.cwd(), resolvedPath) || "."}`);
44
78
  console.log();
45
79
 
46
80
  const filePath = await findEvalsFile(resolvedPath);
@@ -73,28 +107,84 @@ export async function publish(targetPath?: string): Promise<void> {
73
107
  warn(w);
74
108
  }
75
109
 
110
+ const parsed = JSON.parse(content);
111
+
112
+ const displayName = parsed.displayName ?? parsed.skill_name ?? parsed.name ?? "";
113
+ const slug =
114
+ parsed.slug ??
115
+ displayName
116
+ .toLowerCase()
117
+ .replace(/[^a-z0-9]+/g, "-")
118
+ .replace(/^-|-$/g, "");
119
+
76
120
  console.log();
77
121
  console.log(chalk.bold(" Publish summary:"));
78
122
  console.log();
79
-
80
- if (result.summary["skill_name"]) {
81
- dim(`Skill: ${result.summary["skill_name"]}`);
82
- }
83
- if (result.summary["name"]) {
84
- dim(`Name: ${result.summary["name"]}`);
85
- }
86
- if (result.summary["version"]) {
87
- dim(`Version: ${result.summary["version"]}`);
88
- }
89
- if (result.summary["description"]) {
90
- dim(`Description: ${result.summary["description"]}`);
91
- }
123
+ dim(`Name: ${displayName || "(unnamed)"}`);
124
+ dim(`Slug: ${slug || "(auto)"}`);
125
+ dim(`Version: ${parsed.version ?? "1.0.0"}`);
126
+ dim(`Domain: ${parsed.domain ?? "general"}`);
92
127
  dim(`Format: ${getFramework(result.format)?.meta.name ?? result.format}`);
93
128
  dim(`Eval count: ${result.evalCount}`);
94
- dim(`File: ${path.relative(process.cwd(), filePath)}`);
95
-
96
- console.log();
97
- warn("Dry run — publishing is not yet connected to the registry");
98
- success("File is valid and ready to publish");
129
+ dim(`Author: ${auth.handle}`);
99
130
  console.log();
100
- }
131
+
132
+ const { confirm } = await prompts({
133
+ type: "confirm",
134
+ name: "confirm",
135
+ message: "Publish to evalify.sh?",
136
+ initial: true,
137
+ });
138
+
139
+ if (!confirm) {
140
+ console.log();
141
+ dim("Cancelled.");
142
+ console.log();
143
+ return;
144
+ }
145
+
146
+ info("Uploading...");
147
+
148
+ try {
149
+ const token = await getValidToken();
150
+ if (!token) {
151
+ error("Session expired — run: evalify-cli login");
152
+ return;
153
+ }
154
+
155
+ const res = await fetch(REGISTRY_URL, {
156
+ method: "POST",
157
+ headers: {
158
+ "Content-Type": "application/json",
159
+ Authorization: `Bearer ${token}`,
160
+ },
161
+ body: JSON.stringify({
162
+ slug,
163
+ displayName,
164
+ domain: parsed.domain ?? "general",
165
+ version: parsed.version ?? "1.0.0",
166
+ tags: parsed.tags ?? [],
167
+ description: parsed.description ?? "",
168
+ evals: parsed.evals ?? [],
169
+ }),
170
+ });
171
+
172
+ const data = await res.json();
173
+
174
+ if (!res.ok) {
175
+ console.log();
176
+ error(data.error ?? `Server error ${res.status}`);
177
+ console.log();
178
+ return;
179
+ }
180
+
181
+ console.log();
182
+ success(`Published ${displayName || slug}`);
183
+ dim(`View at: https://evalify.sh/criteria/${data.slug}`);
184
+ console.log();
185
+ } catch (err) {
186
+ console.log();
187
+ error(`Failed to publish: ${(err as Error).message}`);
188
+ console.log();
189
+ }
190
+ }
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
4
4
  import prompts from "prompts";
5
5
  import { header, success, info, dim, error } from "../format.js";
6
6
 
7
- const REGISTRY_URL = "https://evalify.sh/api/registry";
7
+ const REGISTRY_URL = "https://www.evalify.sh/api/registry";
8
8
 
9
9
  async function detectSkills(skillsDir: string): Promise<{ name: string; evalCount: number }[]> {
10
10
  try {
@@ -235,7 +235,7 @@ export async function pull(slug: string): Promise<void> {
235
235
  console.log();
236
236
  dim(`Author: ${pack.author}`);
237
237
  dim(`Domain: ${pack.domain}`);
238
- dim(`To validate: evalify validate ${targetDir}`);
238
+ dim(`To validate: evalify-cli validate ${targetDir}`);
239
239
  } catch (err) {
240
240
  error(`Failed to write file: ${(err as Error).message}`);
241
241
  }
@@ -18,6 +18,6 @@ export async function search(query: string): Promise<void> {
18
18
  table(results);
19
19
  console.log();
20
20
  dim(`Showing placeholder results — registry search not yet connected`);
21
- dim(`Use: evalify pull <slug> to download criteria`);
21
+ dim(`Use: evalify-cli pull <slug> to download criteria`);
22
22
  console.log();
23
23
  }
package/src/format.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import chalk from "chalk";
2
2
 
3
- export const VERSION = "0.1.0";
3
+ declare const __PKG_VERSION__: string;
4
+ export const VERSION = __PKG_VERSION__;
4
5
 
5
6
  export function header(): void {
6
- console.log(chalk.bold.cyan(`\nevalify`) + chalk.dim(` v${VERSION}\n`));
7
+ console.log(chalk.bold.cyan(`\nevalify-cli`) + chalk.dim(` v${VERSION}\n`));
7
8
  }
8
9
 
9
10
  export function success(msg: string): void {
package/src/index.ts CHANGED
@@ -4,16 +4,31 @@ import { Command } from "commander";
4
4
  import { VERSION } from "./format.js";
5
5
  import { pull } from "./commands/pull.js";
6
6
  import { publish } from "./commands/publish.js";
7
+ import { login, logout } from "./commands/login.js";
7
8
  import { search } from "./commands/search.js";
8
9
  import { validate } from "./commands/validate.js";
9
10
 
10
11
  const program = new Command();
11
12
 
12
13
  program
13
- .name("evalify")
14
+ .name("evalify-cli")
14
15
  .description("CLI tool for the Evalify eval criteria registry")
15
16
  .version(VERSION);
16
17
 
18
+ program
19
+ .command("login")
20
+ .description("Authenticate with the Evalify registry")
21
+ .action(async () => {
22
+ await login();
23
+ });
24
+
25
+ program
26
+ .command("logout")
27
+ .description("Remove saved credentials")
28
+ .action(async () => {
29
+ await logout();
30
+ });
31
+
17
32
  program
18
33
  .command("pull <slug>")
19
34
  .description("Download eval criteria from the registry")
@@ -42,4 +57,4 @@ program
42
57
  await validate(targetPath);
43
58
  });
44
59
 
45
- program.parse();
60
+ program.parseAsync().then(() => process.exit(0));
package/tsup.config.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { defineConfig } from "tsup";
2
+ import { readFileSync } from "node:fs";
3
+
4
+ const pkg = JSON.parse(readFileSync("./package.json", "utf-8")) as { version: string };
2
5
 
3
6
  export default defineConfig({
7
+ define: { __PKG_VERSION__: JSON.stringify(pkg.version) },
4
8
  entry: ["src/index.ts"],
5
9
  format: ["cjs"],
6
10
  platform: "node",