evalify-cli 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +340 -78
- package/package.json +1 -1
- package/src/commands/login.ts +181 -0
- package/src/commands/publish.ts +169 -21
- package/src/commands/pull.ts +2 -2
- package/src/commands/search.ts +1 -1
- package/src/format.ts +3 -2
- package/src/index.ts +17 -2
- package/tsup.config.ts +4 -0
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
|
|
969
|
-
var
|
|
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 =
|
|
1902
|
-
if (
|
|
1903
|
-
if (sourceExt.includes(
|
|
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) =>
|
|
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 =
|
|
1917
|
+
resolvedScriptPath = fs5.realpathSync(this._scriptPath);
|
|
1918
1918
|
} catch (err) {
|
|
1919
1919
|
resolvedScriptPath = this._scriptPath;
|
|
1920
1920
|
}
|
|
1921
|
-
executableDir =
|
|
1922
|
-
|
|
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 =
|
|
1929
|
+
const legacyName = path5.basename(
|
|
1930
1930
|
this._scriptPath,
|
|
1931
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
2797
|
-
if (
|
|
2798
|
-
this._executableDir =
|
|
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
|
|
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 (
|
|
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
|
|
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:
|
|
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
|
|
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 (
|
|
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
|
|
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:
|
|
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.
|
|
8397
|
+
var VERSION = "0.1.5";
|
|
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
|
|
8622
|
-
var
|
|
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">✓ Authentication successful</h2><p style="color:#71717a">You can close this tab and return to your terminal.</p>` : `<h2 style="color:#ef4444">✗ 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
|
|
8953
|
+
const stat = await import_promises3.default.stat(targetPath);
|
|
8816
8954
|
if (stat.isFile()) return targetPath;
|
|
8817
8955
|
const candidates = [
|
|
8818
|
-
|
|
8819
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
8999
|
+
await import_promises3.default.access(resolvedPath);
|
|
8835
9000
|
} catch {
|
|
8836
|
-
error(`Path not found: ${
|
|
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(
|
|
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 ${
|
|
8852
|
-
const content = await
|
|
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,118 @@ 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
|
-
|
|
8870
|
-
|
|
8871
|
-
}
|
|
8872
|
-
|
|
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(`
|
|
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
|
+
let version = parsed.version ?? "1.0.0";
|
|
9066
|
+
const buildPayload = () => ({
|
|
9067
|
+
slug,
|
|
9068
|
+
displayName,
|
|
9069
|
+
domain: parsed.domain ?? "general",
|
|
9070
|
+
version,
|
|
9071
|
+
tags: parsed.tags ?? [],
|
|
9072
|
+
description: parsed.description ?? "",
|
|
9073
|
+
evals: parsed.evals ?? []
|
|
9074
|
+
});
|
|
9075
|
+
let res = await fetch(REGISTRY_URL2, {
|
|
9076
|
+
method: "POST",
|
|
9077
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
9078
|
+
body: JSON.stringify(buildPayload())
|
|
9079
|
+
});
|
|
9080
|
+
let data = await res.json();
|
|
9081
|
+
if (res.status === 409 && data.conflict === "version") {
|
|
9082
|
+
console.log();
|
|
9083
|
+
const parts = version.split(".").map(Number);
|
|
9084
|
+
const nextPatch = `${parts[0]}.${parts[1]}.${(parts[2] ?? 0) + 1}`;
|
|
9085
|
+
const { choice } = await (0, import_prompts2.default)({
|
|
9086
|
+
type: "select",
|
|
9087
|
+
name: "choice",
|
|
9088
|
+
message: `Version ${version} is already published. What do you want to do?`,
|
|
9089
|
+
choices: [
|
|
9090
|
+
{ title: `Bump to ${nextPatch}`, value: "bump" },
|
|
9091
|
+
{ title: "Set manually", value: "manual" },
|
|
9092
|
+
{ title: "Cancel", value: "cancel" }
|
|
9093
|
+
],
|
|
9094
|
+
initial: 0
|
|
9095
|
+
});
|
|
9096
|
+
if (!choice || choice === "cancel") {
|
|
9097
|
+
console.log();
|
|
9098
|
+
dim("Cancelled.");
|
|
9099
|
+
console.log();
|
|
9100
|
+
return;
|
|
9101
|
+
}
|
|
9102
|
+
if (choice === "manual") {
|
|
9103
|
+
const { manualVersion } = await (0, import_prompts2.default)({
|
|
9104
|
+
type: "text",
|
|
9105
|
+
name: "manualVersion",
|
|
9106
|
+
message: "Enter version:",
|
|
9107
|
+
initial: nextPatch
|
|
9108
|
+
});
|
|
9109
|
+
if (!manualVersion) {
|
|
9110
|
+
console.log();
|
|
9111
|
+
dim("Cancelled.");
|
|
9112
|
+
console.log();
|
|
9113
|
+
return;
|
|
9114
|
+
}
|
|
9115
|
+
version = manualVersion.trim();
|
|
9116
|
+
} else {
|
|
9117
|
+
version = nextPatch;
|
|
9118
|
+
}
|
|
9119
|
+
parsed.version = version;
|
|
9120
|
+
await import_promises3.default.writeFile(filePath, JSON.stringify({ ...parsed }, null, 2) + "\n");
|
|
9121
|
+
info(`Uploading v${version}...`);
|
|
9122
|
+
res = await fetch(REGISTRY_URL2, {
|
|
9123
|
+
method: "POST",
|
|
9124
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
9125
|
+
body: JSON.stringify(buildPayload())
|
|
9126
|
+
});
|
|
9127
|
+
data = await res.json();
|
|
9128
|
+
}
|
|
9129
|
+
if (!res.ok) {
|
|
9130
|
+
console.log();
|
|
9131
|
+
error(data.error ?? `Server error ${res.status}`);
|
|
9132
|
+
console.log();
|
|
9133
|
+
return;
|
|
9134
|
+
}
|
|
9135
|
+
console.log();
|
|
9136
|
+
success(`Published ${displayName || slug}`);
|
|
9137
|
+
dim(`View at: https://evalify.sh/criteria/${data.slug}`);
|
|
9138
|
+
console.log();
|
|
9139
|
+
} catch (err) {
|
|
9140
|
+
console.log();
|
|
9141
|
+
error(`Failed to publish: ${err.message}`);
|
|
9142
|
+
console.log();
|
|
9143
|
+
}
|
|
8888
9144
|
}
|
|
8889
9145
|
|
|
8890
9146
|
// src/commands/search.ts
|
|
@@ -8902,13 +9158,13 @@ async function search(query) {
|
|
|
8902
9158
|
table(results);
|
|
8903
9159
|
console.log();
|
|
8904
9160
|
dim(`Showing placeholder results \u2014 registry search not yet connected`);
|
|
8905
|
-
dim(`Use: evalify pull <slug> to download criteria`);
|
|
9161
|
+
dim(`Use: evalify-cli pull <slug> to download criteria`);
|
|
8906
9162
|
console.log();
|
|
8907
9163
|
}
|
|
8908
9164
|
|
|
8909
9165
|
// src/commands/validate.ts
|
|
8910
|
-
var
|
|
8911
|
-
var
|
|
9166
|
+
var import_node_path4 = __toESM(require("path"));
|
|
9167
|
+
var import_promises4 = __toESM(require("fs/promises"));
|
|
8912
9168
|
var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8913
9169
|
".json",
|
|
8914
9170
|
".md",
|
|
@@ -8928,17 +9184,17 @@ var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
8928
9184
|
".toml"
|
|
8929
9185
|
]);
|
|
8930
9186
|
async function findEvalsJson(targetPath) {
|
|
8931
|
-
const stat = await
|
|
9187
|
+
const stat = await import_promises4.default.stat(targetPath);
|
|
8932
9188
|
if (stat.isFile()) {
|
|
8933
|
-
return { evalsPath: targetPath, rootDir:
|
|
9189
|
+
return { evalsPath: targetPath, rootDir: import_node_path4.default.dirname(targetPath) };
|
|
8934
9190
|
}
|
|
8935
9191
|
const candidates = [
|
|
8936
|
-
|
|
8937
|
-
|
|
9192
|
+
import_node_path4.default.join(targetPath, "evals", "evals.json"),
|
|
9193
|
+
import_node_path4.default.join(targetPath, "evals.json")
|
|
8938
9194
|
];
|
|
8939
9195
|
for (const candidate of candidates) {
|
|
8940
9196
|
try {
|
|
8941
|
-
await
|
|
9197
|
+
await import_promises4.default.access(candidate);
|
|
8942
9198
|
return { evalsPath: candidate, rootDir: targetPath };
|
|
8943
9199
|
} catch {
|
|
8944
9200
|
}
|
|
@@ -8947,19 +9203,19 @@ async function findEvalsJson(targetPath) {
|
|
|
8947
9203
|
}
|
|
8948
9204
|
async function scanCompanionFiles(rootDir, evalsPath, maxDepth = 2) {
|
|
8949
9205
|
const companions = [];
|
|
8950
|
-
const evalsRelative =
|
|
9206
|
+
const evalsRelative = import_node_path4.default.relative(rootDir, evalsPath);
|
|
8951
9207
|
async function walk(dir, depth) {
|
|
8952
9208
|
if (depth > maxDepth) return;
|
|
8953
|
-
const entries = await
|
|
9209
|
+
const entries = await import_promises4.default.readdir(dir, { withFileTypes: true });
|
|
8954
9210
|
for (const entry of entries) {
|
|
8955
9211
|
if (entry.name.startsWith(".")) continue;
|
|
8956
|
-
const fullPath =
|
|
8957
|
-
const relativePath =
|
|
9212
|
+
const fullPath = import_node_path4.default.join(dir, entry.name);
|
|
9213
|
+
const relativePath = import_node_path4.default.relative(rootDir, fullPath);
|
|
8958
9214
|
if (entry.isDirectory()) {
|
|
8959
9215
|
await walk(fullPath, depth + 1);
|
|
8960
9216
|
} else if (entry.isFile()) {
|
|
8961
9217
|
if (relativePath === evalsRelative) continue;
|
|
8962
|
-
const ext =
|
|
9218
|
+
const ext = import_node_path4.default.extname(entry.name).toLowerCase();
|
|
8963
9219
|
if (ALLOWED_EXTENSIONS.has(ext)) {
|
|
8964
9220
|
companions.push(relativePath);
|
|
8965
9221
|
}
|
|
@@ -8984,20 +9240,20 @@ function extractFileRefs(content) {
|
|
|
8984
9240
|
}
|
|
8985
9241
|
async function validate(targetPath) {
|
|
8986
9242
|
header();
|
|
8987
|
-
const resolvedPath =
|
|
9243
|
+
const resolvedPath = import_node_path4.default.resolve(process.cwd(), targetPath || ".");
|
|
8988
9244
|
try {
|
|
8989
|
-
await
|
|
9245
|
+
await import_promises4.default.access(resolvedPath);
|
|
8990
9246
|
} catch {
|
|
8991
9247
|
error(`Path not found: ${targetPath || "."}`);
|
|
8992
9248
|
console.log();
|
|
8993
9249
|
return;
|
|
8994
9250
|
}
|
|
8995
|
-
const stat = await
|
|
9251
|
+
const stat = await import_promises4.default.stat(resolvedPath);
|
|
8996
9252
|
const isFolder = stat.isDirectory();
|
|
8997
9253
|
if (isFolder) {
|
|
8998
|
-
info(`Scanning folder: ${
|
|
9254
|
+
info(`Scanning folder: ${import_node_path4.default.basename(resolvedPath)}/`);
|
|
8999
9255
|
} else {
|
|
9000
|
-
info(`Validating: ${
|
|
9256
|
+
info(`Validating: ${import_node_path4.default.basename(resolvedPath)}`);
|
|
9001
9257
|
}
|
|
9002
9258
|
const found = await findEvalsJson(resolvedPath);
|
|
9003
9259
|
if (!found) {
|
|
@@ -9010,7 +9266,7 @@ async function validate(targetPath) {
|
|
|
9010
9266
|
return;
|
|
9011
9267
|
}
|
|
9012
9268
|
const { evalsPath, rootDir } = found;
|
|
9013
|
-
const relEvalsPath =
|
|
9269
|
+
const relEvalsPath = import_node_path4.default.relative(process.cwd(), evalsPath);
|
|
9014
9270
|
success(`Found ${relEvalsPath}`);
|
|
9015
9271
|
const companions = isFolder ? await scanCompanionFiles(rootDir, evalsPath) : [];
|
|
9016
9272
|
if (companions.length > 0) {
|
|
@@ -9019,7 +9275,7 @@ async function validate(targetPath) {
|
|
|
9019
9275
|
dim(c);
|
|
9020
9276
|
}
|
|
9021
9277
|
}
|
|
9022
|
-
const content = await
|
|
9278
|
+
const content = await import_promises4.default.readFile(evalsPath, "utf-8");
|
|
9023
9279
|
const result = validateEvalsJson(content);
|
|
9024
9280
|
const fileRefs = extractFileRefs(content);
|
|
9025
9281
|
if (fileRefs.length > 0) {
|
|
@@ -9078,7 +9334,13 @@ async function validate(targetPath) {
|
|
|
9078
9334
|
|
|
9079
9335
|
// src/index.ts
|
|
9080
9336
|
var program2 = new Command();
|
|
9081
|
-
program2.name("evalify").description("CLI tool for the Evalify eval criteria registry").version(VERSION);
|
|
9337
|
+
program2.name("evalify-cli").description("CLI tool for the Evalify eval criteria registry").version(VERSION);
|
|
9338
|
+
program2.command("login").description("Authenticate with the Evalify registry").action(async () => {
|
|
9339
|
+
await login();
|
|
9340
|
+
});
|
|
9341
|
+
program2.command("logout").description("Remove saved credentials").action(async () => {
|
|
9342
|
+
await logout();
|
|
9343
|
+
});
|
|
9082
9344
|
program2.command("pull <slug>").description("Download eval criteria from the registry").action(async (slug) => {
|
|
9083
9345
|
await pull(slug);
|
|
9084
9346
|
});
|
|
@@ -9091,4 +9353,4 @@ program2.command("search <query>").description("Search the registry for eval cri
|
|
|
9091
9353
|
program2.command("validate [path]").description("Validate evals.json from a file or skill folder").action(async (targetPath) => {
|
|
9092
9354
|
await validate(targetPath);
|
|
9093
9355
|
});
|
|
9094
|
-
program2.
|
|
9356
|
+
program2.parseAsync().then(() => process.exit(0));
|
package/package.json
CHANGED
|
@@ -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">✓ Authentication successful</h2><p style="color:#71717a">You can close this tab and return to your terminal.</p>`
|
|
144
|
+
: `<h2 style="color:#ef4444">✗ 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
|
+
}
|
package/src/commands/publish.ts
CHANGED
|
@@ -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: ${
|
|
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(
|
|
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,142 @@ 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
|
-
|
|
81
|
-
|
|
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(`
|
|
95
|
-
|
|
129
|
+
dim(`Author: ${auth.handle}`);
|
|
96
130
|
console.log();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
+
let version = parsed.version ?? "1.0.0";
|
|
156
|
+
|
|
157
|
+
const buildPayload = () => ({
|
|
158
|
+
slug,
|
|
159
|
+
displayName,
|
|
160
|
+
domain: parsed.domain ?? "general",
|
|
161
|
+
version,
|
|
162
|
+
tags: parsed.tags ?? [],
|
|
163
|
+
description: parsed.description ?? "",
|
|
164
|
+
evals: parsed.evals ?? [],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
let res = await fetch(REGISTRY_URL, {
|
|
168
|
+
method: "POST",
|
|
169
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
170
|
+
body: JSON.stringify(buildPayload()),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
let data = await res.json();
|
|
174
|
+
|
|
175
|
+
// Version conflict — offer to bump and retry
|
|
176
|
+
if (res.status === 409 && data.conflict === "version") {
|
|
177
|
+
console.log();
|
|
178
|
+
const parts = version.split(".").map(Number);
|
|
179
|
+
const nextPatch = `${parts[0]}.${parts[1]}.${(parts[2] ?? 0) + 1}`;
|
|
180
|
+
|
|
181
|
+
const { choice } = await prompts({
|
|
182
|
+
type: "select",
|
|
183
|
+
name: "choice",
|
|
184
|
+
message: `Version ${version} is already published. What do you want to do?`,
|
|
185
|
+
choices: [
|
|
186
|
+
{ title: `Bump to ${nextPatch}`, value: "bump" },
|
|
187
|
+
{ title: "Set manually", value: "manual" },
|
|
188
|
+
{ title: "Cancel", value: "cancel" },
|
|
189
|
+
],
|
|
190
|
+
initial: 0,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (!choice || choice === "cancel") {
|
|
194
|
+
console.log();
|
|
195
|
+
dim("Cancelled.");
|
|
196
|
+
console.log();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (choice === "manual") {
|
|
201
|
+
const { manualVersion } = await prompts({
|
|
202
|
+
type: "text",
|
|
203
|
+
name: "manualVersion",
|
|
204
|
+
message: "Enter version:",
|
|
205
|
+
initial: nextPatch,
|
|
206
|
+
});
|
|
207
|
+
if (!manualVersion) {
|
|
208
|
+
console.log();
|
|
209
|
+
dim("Cancelled.");
|
|
210
|
+
console.log();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
version = manualVersion.trim();
|
|
214
|
+
} else {
|
|
215
|
+
version = nextPatch;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Write new version back to evals.json
|
|
219
|
+
parsed.version = version;
|
|
220
|
+
await fs.writeFile(filePath, JSON.stringify({ ...parsed }, null, 2) + "\n");
|
|
221
|
+
|
|
222
|
+
info(`Uploading v${version}...`);
|
|
223
|
+
|
|
224
|
+
res = await fetch(REGISTRY_URL, {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
227
|
+
body: JSON.stringify(buildPayload()),
|
|
228
|
+
});
|
|
229
|
+
data = await res.json();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!res.ok) {
|
|
233
|
+
console.log();
|
|
234
|
+
error(data.error ?? `Server error ${res.status}`);
|
|
235
|
+
console.log();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
console.log();
|
|
240
|
+
success(`Published ${displayName || slug}`);
|
|
241
|
+
dim(`View at: https://evalify.sh/criteria/${data.slug}`);
|
|
242
|
+
console.log();
|
|
243
|
+
} catch (err) {
|
|
244
|
+
console.log();
|
|
245
|
+
error(`Failed to publish: ${(err as Error).message}`);
|
|
246
|
+
console.log();
|
|
247
|
+
}
|
|
248
|
+
}
|
package/src/commands/pull.ts
CHANGED
|
@@ -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
|
}
|
package/src/commands/search.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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",
|