mu-agent 0.2.2 → 0.2.3
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/CHANGELOG.md +25 -0
- package/README.md +3 -7
- package/dist/bundle/chunks/anthropic.js +1 -1
- package/dist/bundle/chunks/chunk-FGOMFVJ2.js +2 -0
- package/dist/bundle/chunks/{chunk-BK7M6BDT.js → chunk-HPQUXAZK.js} +6 -6
- package/dist/bundle/chunks/openai-chatgpt.js +1 -1
- package/dist/bundle/chunks/openai-codex.js +2 -2
- package/dist/bundle/chunks/openrouter.js +1 -1
- package/dist/bundle/chunks/radius.js +1 -1
- package/dist/bundle/chunks/{virtual-modules-U6MRV5BC.js → virtual-modules-6BAN7L2H.js} +1 -1
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/judge/dist/auth.js +60 -15
- package/judge/dist/mude.js +624 -117
- package/judge/dist/setup.js +275 -146
- package/judge/manifest.json +14 -15
- package/judge/mu/{chunk-6XRUK3PM.js → chunk-JZ4X4HQO.js} +1 -1
- package/judge/mu/{cloud-code-XR7SZMJC.js → cloud-code-PDKLWJEG.js} +1 -1
- package/judge/mu/index.js +42 -24
- package/kyrn/bin/kyrn-doctor +0 -1
- package/kyrn/bin/kyrn-ledger +0 -2
- package/kyrn/bin/mu.mjs +50 -16
- package/package.json +2 -2
- package/dist/bundle/chunks/chunk-AN7MSYCK.js +0 -2
package/judge/dist/mude.js
CHANGED
|
@@ -3,9 +3,9 @@ import { createRequire as __muCreateRequire } from "node:module"; const require
|
|
|
3
3
|
// packages/kyrn-judge/src/mude/command.ts
|
|
4
4
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
6
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync6, readdirSync as readdirSync2, readFileSync as readFileSync7, rmSync as rmSync4, statfsSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
7
7
|
import { homedir as homedir3, release, totalmem as totalmem2 } from "node:os";
|
|
8
|
-
import { join as
|
|
8
|
+
import { join as join7 } from "node:path";
|
|
9
9
|
|
|
10
10
|
// packages/kyrn-judge/src/mude/config.ts
|
|
11
11
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
@@ -338,8 +338,8 @@ var FILES = [
|
|
|
338
338
|
];
|
|
339
339
|
var MUDE_FILES = FILES.map(([path, size, sha256]) => ({ path, size, sha256 }));
|
|
340
340
|
var MUDE_BYTES = MUDE_FILES.reduce((sum, file) => sum + file.size, 0);
|
|
341
|
-
function
|
|
342
|
-
return `${endpoint.replace(/\/+$/, "")}/${
|
|
341
|
+
function fileUrl(repo, revision, path, endpoint = "https://huggingface.co") {
|
|
342
|
+
return `${endpoint.replace(/\/+$/, "")}/${repo}/resolve/${revision}/${path.split("/").map(encodeURIComponent).join("/")}`;
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
// packages/kyrn-judge/src/mude/server.ts
|
|
@@ -445,7 +445,7 @@ async function startServer(paths, options) {
|
|
|
445
445
|
"--port",
|
|
446
446
|
String(options.port),
|
|
447
447
|
"--name",
|
|
448
|
-
MUDE_MODEL,
|
|
448
|
+
options.name ?? MUDE_MODEL,
|
|
449
449
|
"--verify",
|
|
450
450
|
"fast"
|
|
451
451
|
],
|
|
@@ -526,6 +526,11 @@ function serverMemory(pid) {
|
|
|
526
526
|
var ROUTED_AWAY = ["tool.description", "tool.injection"];
|
|
527
527
|
var MUDE = "mude";
|
|
528
528
|
var KEY_VARIABLE = "MU_JUDGE_MUDE_KEY";
|
|
529
|
+
var MUDE_JUDGE = {
|
|
530
|
+
name: MUDE,
|
|
531
|
+
keyVariable: KEY_VARIABLE,
|
|
532
|
+
entry: (port2) => port2 === DEFAULT_PORT ? void 0 : entryFor(port2)
|
|
533
|
+
};
|
|
529
534
|
function readState(paths) {
|
|
530
535
|
try {
|
|
531
536
|
const value = JSON.parse(readFileSync3(paths.state, "utf8"));
|
|
@@ -538,18 +543,22 @@ function writeState(paths, state) {
|
|
|
538
543
|
writeFileSync3(paths.state, `${JSON.stringify(state, null, 2)}
|
|
539
544
|
`, { mode: 384 });
|
|
540
545
|
}
|
|
541
|
-
function judgeSetting(files) {
|
|
546
|
+
function judgeSetting(files, name = MUDE) {
|
|
542
547
|
const file = readJsonFile(judgeConfigPath(files.agentDir));
|
|
543
548
|
if (file.state === "unreadable") return { enabled: false, localOnly: false, fallback: [], unreadable: true };
|
|
544
549
|
const config = file.state === "ok" ? file.value : {};
|
|
545
550
|
const tiers = tiersOf(config);
|
|
546
|
-
const enabled = tiers[0] ===
|
|
547
|
-
const fallback = tiers.filter((tier) => tier !==
|
|
551
|
+
const enabled = tiers[0] === name && !judgeOff(config);
|
|
552
|
+
const fallback = tiers.filter((tier) => tier !== name);
|
|
548
553
|
return { enabled, localOnly: enabled && fallback.length === 0, fallback };
|
|
549
554
|
}
|
|
550
|
-
function mudeInTiers(files) {
|
|
555
|
+
function mudeInTiers(files, name = MUDE) {
|
|
556
|
+
const file = readJsonFile(judgeConfigPath(files.agentDir));
|
|
557
|
+
return file.state === "ok" && tiersOf(file.value).includes(name);
|
|
558
|
+
}
|
|
559
|
+
function currentTiers(files) {
|
|
551
560
|
const file = readJsonFile(judgeConfigPath(files.agentDir));
|
|
552
|
-
return file.state === "ok"
|
|
561
|
+
return file.state === "ok" ? tiersOf(file.value) : [];
|
|
553
562
|
}
|
|
554
563
|
function readConfig(files) {
|
|
555
564
|
const path = judgeConfigPath(files.agentDir);
|
|
@@ -583,18 +592,20 @@ function withoutOwnRoutes(config, state) {
|
|
|
583
592
|
function entryFor(port2) {
|
|
584
593
|
return { ...BUILT_IN_JUDGES[MUDE], baseUrl: mudeBaseUrl(port2) };
|
|
585
594
|
}
|
|
586
|
-
function
|
|
595
|
+
function enableLocal(judge, paths, files, options) {
|
|
596
|
+
const name = judge.name;
|
|
587
597
|
const { path, config } = readConfig(files);
|
|
588
598
|
const state = readState(paths);
|
|
589
|
-
const current = tiersOf(config).filter((tier) => tier !==
|
|
599
|
+
const current = tiersOf(config).filter((tier) => tier !== name);
|
|
590
600
|
const before = current.length > 0 ? current : [...state?.previousTiers ?? ["jev"]];
|
|
591
|
-
const tiers = options.localOnly ? [
|
|
601
|
+
const tiers = options.localOnly ? [name] : [name, ...before.filter((tier) => tier !== name)];
|
|
592
602
|
const judgeWasOff = state?.judgeWasOff ?? judgeOff(config);
|
|
593
603
|
let next = withJudge(withTiers(withoutOwnRoutes(config, state), tiers), true);
|
|
594
604
|
const judges = judgesOf(next);
|
|
595
|
-
const
|
|
596
|
-
if (
|
|
597
|
-
else if (state?.wroteEntry) delete judges[
|
|
605
|
+
const written = judge.entry(options.port);
|
|
606
|
+
if (written) judges[name] = written;
|
|
607
|
+
else if (state?.wroteEntry) delete judges[name];
|
|
608
|
+
const entry = written !== void 0;
|
|
598
609
|
next = withSection(next, "judges", judges);
|
|
599
610
|
const fallback = tiers.slice(1);
|
|
600
611
|
const routes = recordOf(next.routes);
|
|
@@ -610,9 +621,9 @@ function enableMude(paths, files, options) {
|
|
|
610
621
|
const backups = new Backups(files.home, files.now ?? /* @__PURE__ */ new Date());
|
|
611
622
|
backups.save(path);
|
|
612
623
|
writeJsonFile(path, next);
|
|
613
|
-
if (envFileValue(readText(files.envFile),
|
|
624
|
+
if (envFileValue(readText(files.envFile), judge.keyVariable) !== options.key) {
|
|
614
625
|
backups.save(files.envFile);
|
|
615
|
-
writeEnvValue(files.envFile,
|
|
626
|
+
writeEnvValue(files.envFile, judge.keyVariable, options.key);
|
|
616
627
|
}
|
|
617
628
|
writeState(paths, {
|
|
618
629
|
port: options.port,
|
|
@@ -624,15 +635,18 @@ function enableMude(paths, files, options) {
|
|
|
624
635
|
return tiers;
|
|
625
636
|
}
|
|
626
637
|
function disableMude(paths, files, options = {}) {
|
|
638
|
+
return disableLocal(MUDE_JUDGE, paths, files, options);
|
|
639
|
+
}
|
|
640
|
+
function disableLocal(judge, paths, files, options = {}) {
|
|
627
641
|
const { path, config } = readConfig(files);
|
|
628
642
|
const state = readState(paths);
|
|
629
|
-
const rest = tiersOf(config).filter((tier) => tier !==
|
|
643
|
+
const rest = tiersOf(config).filter((tier) => tier !== judge.name);
|
|
630
644
|
const tiers = rest.length > 0 ? rest : [...state?.previousTiers ?? ["jev"]];
|
|
631
645
|
let next = withTiers(withoutOwnRoutes(config, state), tiers);
|
|
632
646
|
if (state?.judgeWasOff) next = withJudge(next, false);
|
|
633
647
|
if (options.removeEntry && state?.wroteEntry) {
|
|
634
648
|
const judges = judgesOf(next);
|
|
635
|
-
delete judges[
|
|
649
|
+
delete judges[judge.name];
|
|
636
650
|
next = withSection(next, "judges", judges);
|
|
637
651
|
}
|
|
638
652
|
new Backups(files.home, files.now ?? /* @__PURE__ */ new Date()).save(path);
|
|
@@ -698,6 +712,14 @@ async function sha256File(path) {
|
|
|
698
712
|
await pipeline(createReadStream(path), hash);
|
|
699
713
|
return hash.digest("hex");
|
|
700
714
|
}
|
|
715
|
+
var pinnedHash = (file) => file.sha256 ?? file.gitSha1 ?? "";
|
|
716
|
+
async function hashOf(path, file) {
|
|
717
|
+
if (file.sha256 || !file.gitSha1) return sha256File(path);
|
|
718
|
+
const hash = createHash("sha1");
|
|
719
|
+
hash.update(`blob ${statSync3(path).size}\0`);
|
|
720
|
+
await pipeline(createReadStream(path), hash);
|
|
721
|
+
return hash.digest("hex");
|
|
722
|
+
}
|
|
701
723
|
function readVerified(dir) {
|
|
702
724
|
try {
|
|
703
725
|
const value = JSON.parse(readFileSync4(join3(dir, ".mu-verified.json"), "utf8"));
|
|
@@ -708,7 +730,7 @@ function readVerified(dir) {
|
|
|
708
730
|
}
|
|
709
731
|
function knownGood(dir, file, verified) {
|
|
710
732
|
const seen = verified[file.path];
|
|
711
|
-
if (!seen || seen.sha256 !== file
|
|
733
|
+
if (!seen || seen.sha256 !== pinnedHash(file)) return false;
|
|
712
734
|
try {
|
|
713
735
|
const stats = statSync3(join3(dir, file.path));
|
|
714
736
|
return stats.size === file.size && stats.mtimeMs === seen.mtimeMs;
|
|
@@ -718,7 +740,7 @@ function knownGood(dir, file, verified) {
|
|
|
718
740
|
}
|
|
719
741
|
function remember(dir, verified, file) {
|
|
720
742
|
const stats = statSync3(join3(dir, file.path));
|
|
721
|
-
verified[file.path] = { size: stats.size, mtimeMs: stats.mtimeMs, sha256: file
|
|
743
|
+
verified[file.path] = { size: stats.size, mtimeMs: stats.mtimeMs, sha256: pinnedHash(file) };
|
|
722
744
|
writeFileSync4(join3(dir, ".mu-verified.json"), `${JSON.stringify(verified, null, 1)}
|
|
723
745
|
`);
|
|
724
746
|
}
|
|
@@ -769,7 +791,7 @@ async function downloadFiles(options) {
|
|
|
769
791
|
mkdirSync3(dirname2(target), { recursive: true });
|
|
770
792
|
if (existsSync3(target) && statSync3(target).size === file.size) {
|
|
771
793
|
options.onProgress?.({ file: file.path, checking: true, done: settled, total });
|
|
772
|
-
if (await
|
|
794
|
+
if (await hashOf(target, file) === pinnedHash(file)) {
|
|
773
795
|
remember(dir, verified, file);
|
|
774
796
|
settled += file.size;
|
|
775
797
|
continue;
|
|
@@ -780,17 +802,17 @@ async function downloadFiles(options) {
|
|
|
780
802
|
const onBytes = (bytes) => options.onProgress?.({ file: file.path, checking: false, done: settled + bytes, total });
|
|
781
803
|
let resumed = await fetchFile(options, file, part, onBytes);
|
|
782
804
|
options.onProgress?.({ file: file.path, checking: true, done: settled + file.size, total });
|
|
783
|
-
let actual = await
|
|
784
|
-
if (actual !== file
|
|
805
|
+
let actual = await hashOf(part, file);
|
|
806
|
+
if (actual !== pinnedHash(file) && resumed) {
|
|
785
807
|
dropPart(part);
|
|
786
808
|
resumed = await fetchFile(options, file, part, onBytes);
|
|
787
809
|
options.onProgress?.({ file: file.path, checking: true, done: settled + file.size, total });
|
|
788
|
-
actual = await
|
|
810
|
+
actual = await hashOf(part, file);
|
|
789
811
|
}
|
|
790
|
-
if (actual !== file
|
|
812
|
+
if (actual !== pinnedHash(file)) {
|
|
791
813
|
dropPart(part);
|
|
792
814
|
throw new DownloadError(
|
|
793
|
-
`${file.path} does not match its pinned sha256 (got ${actual.slice(0, 12)}\u2026, want ${file.
|
|
815
|
+
`${file.path} does not match its pinned ${file.sha256 ? "sha256" : "git hash"} (got ${actual.slice(0, 12)}\u2026, want ${pinnedHash(file).slice(0, 12)}\u2026); it was removed`,
|
|
794
816
|
"integrity"
|
|
795
817
|
);
|
|
796
818
|
}
|
|
@@ -934,6 +956,10 @@ async function fetchOnce(options, file, part, onBytes) {
|
|
|
934
956
|
}
|
|
935
957
|
}
|
|
936
958
|
|
|
959
|
+
// packages/kyrn-judge/src/mude/models.ts
|
|
960
|
+
import { mkdirSync as mkdirSync4, readdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
961
|
+
import { join as join5 } from "node:path";
|
|
962
|
+
|
|
937
963
|
// packages/kyrn-judge/src/mude/paths.ts
|
|
938
964
|
import { join as join4 } from "node:path";
|
|
939
965
|
function mudePaths(muHome) {
|
|
@@ -957,12 +983,204 @@ function mudePaths(muHome) {
|
|
|
957
983
|
};
|
|
958
984
|
}
|
|
959
985
|
|
|
986
|
+
// packages/kyrn-judge/src/mude/models.ts
|
|
987
|
+
var ModelError = class extends Error {
|
|
988
|
+
kind;
|
|
989
|
+
constructor(message, kind) {
|
|
990
|
+
super(message);
|
|
991
|
+
this.name = "ModelError";
|
|
992
|
+
this.kind = kind;
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
var PART = /^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/;
|
|
996
|
+
function parseModelRef(input) {
|
|
997
|
+
const text = input.trim();
|
|
998
|
+
let repo;
|
|
999
|
+
let revision;
|
|
1000
|
+
if (/^https?:\/\//i.test(text) || /^(www\.)?(huggingface\.co|hf\.co)\//i.test(text)) {
|
|
1001
|
+
let url;
|
|
1002
|
+
try {
|
|
1003
|
+
url = new URL(/^https?:\/\//i.test(text) ? text : `https://${text}`);
|
|
1004
|
+
} catch {
|
|
1005
|
+
throw new ModelError(`${text} isn't a Hugging Face link`, "usage");
|
|
1006
|
+
}
|
|
1007
|
+
if (!/^(www\.)?(huggingface\.co|hf\.co)$/i.test(url.hostname))
|
|
1008
|
+
throw new ModelError(`${text} isn't a Hugging Face link`, "usage");
|
|
1009
|
+
const parts = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
1010
|
+
if (parts[0] === "datasets" || parts[0] === "spaces") {
|
|
1011
|
+
throw new ModelError(
|
|
1012
|
+
`${text} is a Hugging Face ${parts[0] === "datasets" ? "dataset" : "Space"}, not a model`,
|
|
1013
|
+
"usage"
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
if (parts.length < 2)
|
|
1017
|
+
throw new ModelError(
|
|
1018
|
+
`${text} doesn't name a model: use a link like https://huggingface.co/owner/name`,
|
|
1019
|
+
"usage"
|
|
1020
|
+
);
|
|
1021
|
+
repo = `${parts[0]}/${parts[1]}`;
|
|
1022
|
+
if (["tree", "blob", "resolve"].includes(parts[2] ?? "") && parts[3]) revision = parts[3];
|
|
1023
|
+
} else {
|
|
1024
|
+
const at = text.lastIndexOf("@");
|
|
1025
|
+
repo = at > 0 ? text.slice(0, at) : text;
|
|
1026
|
+
revision = at > 0 ? text.slice(at + 1) : void 0;
|
|
1027
|
+
}
|
|
1028
|
+
const [owner, name, extra] = repo.split("/");
|
|
1029
|
+
if (!owner || !name || extra !== void 0 || !PART.test(owner) || !PART.test(name)) {
|
|
1030
|
+
throw new ModelError(
|
|
1031
|
+
`${text} isn't a Hugging Face model: give its link or owner/name (for example qybaihe/mude-1.0-mlx-8bit)`,
|
|
1032
|
+
"usage"
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
if (revision !== void 0 && !/^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$/.test(revision)) {
|
|
1036
|
+
throw new ModelError(`${revision} isn't a branch, tag or commit`, "usage");
|
|
1037
|
+
}
|
|
1038
|
+
return { repo, revision };
|
|
1039
|
+
}
|
|
1040
|
+
var SERVER_FILE = "d3_server_mac.py";
|
|
1041
|
+
async function resolveModel(ref, options = {}) {
|
|
1042
|
+
const endpoint = (options.endpoint ?? "https://huggingface.co").replace(/\/+$/, "");
|
|
1043
|
+
const get = options.fetch ?? fetch;
|
|
1044
|
+
const headers = options.token ? { Authorization: `Bearer ${options.token}` } : {};
|
|
1045
|
+
const request = async (url) => {
|
|
1046
|
+
let response;
|
|
1047
|
+
try {
|
|
1048
|
+
response = await get(url, { headers, signal: AbortSignal.timeout(3e4) });
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
throw new ModelError(
|
|
1051
|
+
`Couldn't reach Hugging Face (${error instanceof Error ? error.message : String(error)}): check the connection, then add it again`,
|
|
1052
|
+
"network"
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
if (response.status === 401 || response.status === 403) {
|
|
1056
|
+
throw new ModelError(
|
|
1057
|
+
`Hugging Face won't show ${ref.repo} (${response.status}): it's private or gated. Accept its terms on huggingface.co, set HF_TOKEN, then add it again`,
|
|
1058
|
+
"access"
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
if (response.status === 404) {
|
|
1062
|
+
throw new ModelError(
|
|
1063
|
+
`Hugging Face has no model ${ref.repo}${ref.revision ? ` at ${ref.revision}` : ""}`,
|
|
1064
|
+
"notFound"
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
if (!response.ok) throw new ModelError(`Hugging Face returned ${response.status} for ${ref.repo}`, "network");
|
|
1068
|
+
return response;
|
|
1069
|
+
};
|
|
1070
|
+
const info = await (await request(`${endpoint}/api/models/${ref.repo}/revision/${encodeURIComponent(ref.revision ?? "main")}`)).json();
|
|
1071
|
+
const revision = typeof info.sha === "string" ? info.sha : "";
|
|
1072
|
+
if (!/^[0-9a-f]{40}$/.test(revision)) throw new Error(`Hugging Face didn't say which commit ${ref.repo} is at`);
|
|
1073
|
+
const files = [];
|
|
1074
|
+
let next = `${endpoint}/api/models/${ref.repo}/tree/${revision}?recursive=true`;
|
|
1075
|
+
while (next) {
|
|
1076
|
+
const response = await request(next);
|
|
1077
|
+
for (const entry of await response.json()) {
|
|
1078
|
+
if (entry.type !== "file" || typeof entry.path !== "string" || typeof entry.size !== "number") continue;
|
|
1079
|
+
if (entry.path.split("/").some((part) => part === ".." || part === "")) continue;
|
|
1080
|
+
const sha256 = entry.lfs?.oid;
|
|
1081
|
+
if (sha256 && /^[0-9a-f]{64}$/.test(sha256)) files.push({ path: entry.path, size: entry.size, sha256 });
|
|
1082
|
+
else if (entry.oid && /^[0-9a-f]{40}$/.test(entry.oid))
|
|
1083
|
+
files.push({ path: entry.path, size: entry.size, gitSha1: entry.oid });
|
|
1084
|
+
else throw new Error(`Hugging Face listed ${entry.path} of ${ref.repo} without a hash, so mu can't check it`);
|
|
1085
|
+
}
|
|
1086
|
+
next = /<([^>]+)>;\s*rel="next"/.exec(response.headers.get("link") ?? "")?.[1];
|
|
1087
|
+
}
|
|
1088
|
+
if (!files.some((file) => file.path === SERVER_FILE)) {
|
|
1089
|
+
throw new ModelError(
|
|
1090
|
+
`${ref.repo} isn't packaged as a decision model mu can run: it has no ${SERVER_FILE}. mu runs models packaged like mude 1.0's MLX edition, with their own System One server`,
|
|
1091
|
+
"notRunnable"
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
return { revision, files };
|
|
1095
|
+
}
|
|
1096
|
+
function reserved(name) {
|
|
1097
|
+
return name in BUILT_IN_JUDGES || name === "off" || name.startsWith("jev") || name.includes(":");
|
|
1098
|
+
}
|
|
1099
|
+
var slug = (text) => text.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1100
|
+
function modelId(repo, taken) {
|
|
1101
|
+
const [owner = "", name = ""] = repo.split("/");
|
|
1102
|
+
const short = slug(name);
|
|
1103
|
+
if (short && !reserved(short) && !taken.has(short)) return short;
|
|
1104
|
+
const long = slug(`${owner}-${name}`);
|
|
1105
|
+
if (!reserved(long) && !taken.has(long)) return long;
|
|
1106
|
+
for (let n = 2; ; n++) if (!taken.has(`${long}-${n}`)) return `${long}-${n}`;
|
|
1107
|
+
}
|
|
1108
|
+
function modelsDir(muHome) {
|
|
1109
|
+
return join5(muHome, "local-judge", "models");
|
|
1110
|
+
}
|
|
1111
|
+
function modelPaths(muHome, record) {
|
|
1112
|
+
const shared = mudePaths(muHome);
|
|
1113
|
+
const root = join5(modelsDir(muHome), record.id);
|
|
1114
|
+
const model = join5(root, "model", record.revision);
|
|
1115
|
+
return {
|
|
1116
|
+
...shared,
|
|
1117
|
+
root,
|
|
1118
|
+
model,
|
|
1119
|
+
verified: join5(model, ".mu-verified.json"),
|
|
1120
|
+
key: join5(root, "key"),
|
|
1121
|
+
pid: join5(root, "server.pid"),
|
|
1122
|
+
log: join5(root, "server.log"),
|
|
1123
|
+
state: join5(root, "state.json"),
|
|
1124
|
+
install: join5(root, "install.json"),
|
|
1125
|
+
installLock: join5(root, "install.lock")
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
function writeRecord(muHome, record) {
|
|
1129
|
+
const root = join5(modelsDir(muHome), record.id);
|
|
1130
|
+
mkdirSync4(root, { recursive: true });
|
|
1131
|
+
writeFileSync5(join5(root, "model.json"), `${JSON.stringify(record, null, 1)}
|
|
1132
|
+
`);
|
|
1133
|
+
}
|
|
1134
|
+
function isRecord2(value) {
|
|
1135
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1136
|
+
const record = value;
|
|
1137
|
+
return typeof record.id === "string" && typeof record.repo === "string" && typeof record.revision === "string" && typeof record.bytes === "number" && Array.isArray(record.files);
|
|
1138
|
+
}
|
|
1139
|
+
function listRecords(muHome) {
|
|
1140
|
+
let names;
|
|
1141
|
+
try {
|
|
1142
|
+
names = readdirSync(modelsDir(muHome));
|
|
1143
|
+
} catch {
|
|
1144
|
+
return [];
|
|
1145
|
+
}
|
|
1146
|
+
const records = [];
|
|
1147
|
+
for (const name of names.sort()) {
|
|
1148
|
+
try {
|
|
1149
|
+
const value = JSON.parse(readFileSync5(join5(modelsDir(muHome), name, "model.json"), "utf8"));
|
|
1150
|
+
if (isRecord2(value) && value.id === name) records.push(value);
|
|
1151
|
+
} catch {
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return records;
|
|
1155
|
+
}
|
|
1156
|
+
function findRecord(muHome, name) {
|
|
1157
|
+
const records = listRecords(muHome);
|
|
1158
|
+
const lower = name.toLowerCase();
|
|
1159
|
+
return records.find((record) => record.id === lower) ?? records.find((record) => record.repo.toLowerCase() === lower);
|
|
1160
|
+
}
|
|
1161
|
+
function keyVariable(id) {
|
|
1162
|
+
return `MU_JUDGE_${id.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_KEY`;
|
|
1163
|
+
}
|
|
1164
|
+
function localJudgeOf(id) {
|
|
1165
|
+
return {
|
|
1166
|
+
name: id,
|
|
1167
|
+
keyVariable: keyVariable(id),
|
|
1168
|
+
entry: (port2) => ({
|
|
1169
|
+
type: "typesafe",
|
|
1170
|
+
baseUrl: mudeBaseUrl(port2),
|
|
1171
|
+
model: id,
|
|
1172
|
+
apiKeyEnv: keyVariable(id),
|
|
1173
|
+
timeoutMs: 4e3
|
|
1174
|
+
})
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
|
|
960
1178
|
// packages/kyrn-judge/src/mude/python.ts
|
|
961
1179
|
import { spawn as spawn2 } from "node:child_process";
|
|
962
1180
|
import { createHash as createHash2 } from "node:crypto";
|
|
963
|
-
import { accessSync, constants, existsSync as existsSync4, mkdirSync as
|
|
1181
|
+
import { accessSync, constants, existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
964
1182
|
import { homedir as homedir2 } from "node:os";
|
|
965
|
-
import { delimiter, join as
|
|
1183
|
+
import { delimiter, join as join6 } from "node:path";
|
|
966
1184
|
var PYTHON_VERSION = "3.13";
|
|
967
1185
|
var UV_RELEASE = {
|
|
968
1186
|
version: "0.13.1",
|
|
@@ -980,14 +1198,14 @@ function executable(path) {
|
|
|
980
1198
|
function findUv(paths, env = process.env, home = homedir2()) {
|
|
981
1199
|
const dirs = [
|
|
982
1200
|
...(env.PATH ?? "").split(delimiter),
|
|
983
|
-
|
|
984
|
-
|
|
1201
|
+
join6(home, ".local", "bin"),
|
|
1202
|
+
join6(home, ".cargo", "bin"),
|
|
985
1203
|
"/opt/homebrew/bin",
|
|
986
1204
|
"/usr/local/bin",
|
|
987
1205
|
paths.tools
|
|
988
1206
|
];
|
|
989
1207
|
for (const dir of dirs) {
|
|
990
|
-
if (dir && executable(
|
|
1208
|
+
if (dir && executable(join6(dir, "uv"))) return join6(dir, "uv");
|
|
991
1209
|
}
|
|
992
1210
|
return void 0;
|
|
993
1211
|
}
|
|
@@ -1017,7 +1235,7 @@ function run(command, args, options = {}) {
|
|
|
1017
1235
|
});
|
|
1018
1236
|
}
|
|
1019
1237
|
async function downloadUv(paths, options = {}) {
|
|
1020
|
-
|
|
1238
|
+
mkdirSync5(paths.tools, { recursive: true });
|
|
1021
1239
|
const archive = "uv.tar.gz";
|
|
1022
1240
|
options.onLine?.(`Downloading uv ${UV_RELEASE.version}`);
|
|
1023
1241
|
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
@@ -1037,27 +1255,27 @@ async function downloadUv(paths, options = {}) {
|
|
|
1037
1255
|
const actual = createHash2("sha256").update(bytes).digest("hex");
|
|
1038
1256
|
if (actual !== UV_RELEASE.sha256)
|
|
1039
1257
|
throw new Error("The downloaded uv does not match its pinned sha256; nothing was installed");
|
|
1040
|
-
|
|
1258
|
+
writeFileSync6(join6(paths.tools, archive), bytes);
|
|
1041
1259
|
const unpacked = await run("/usr/bin/tar", [
|
|
1042
1260
|
"-xzf",
|
|
1043
|
-
|
|
1261
|
+
join6(paths.tools, archive),
|
|
1044
1262
|
"-C",
|
|
1045
1263
|
paths.tools,
|
|
1046
1264
|
"--strip-components",
|
|
1047
1265
|
"1"
|
|
1048
1266
|
]);
|
|
1049
|
-
rmSync3(
|
|
1050
|
-
if (unpacked.code !== 0 || !executable(
|
|
1267
|
+
rmSync3(join6(paths.tools, archive), { force: true });
|
|
1268
|
+
if (unpacked.code !== 0 || !executable(join6(paths.tools, "uv")))
|
|
1051
1269
|
throw new Error(`Couldn't unpack uv: ${unpacked.output}`);
|
|
1052
|
-
return
|
|
1270
|
+
return join6(paths.tools, "uv");
|
|
1053
1271
|
}
|
|
1054
1272
|
function lockDigest(lockFile) {
|
|
1055
|
-
return createHash2("sha256").update(
|
|
1273
|
+
return createHash2("sha256").update(readFileSync6(lockFile)).digest("hex");
|
|
1056
1274
|
}
|
|
1057
1275
|
function venvReady(paths, lockFile) {
|
|
1058
1276
|
if (!existsSync4(paths.python)) return false;
|
|
1059
1277
|
try {
|
|
1060
|
-
return
|
|
1278
|
+
return readFileSync6(paths.venvLock, "utf8").trim() === lockDigest(lockFile);
|
|
1061
1279
|
} catch {
|
|
1062
1280
|
return false;
|
|
1063
1281
|
}
|
|
@@ -1091,7 +1309,7 @@ ${made.output}`);
|
|
|
1091
1309
|
if (installed2.code !== 0)
|
|
1092
1310
|
throw new Error(`Couldn't install mude's Python packages (uv pip install):
|
|
1093
1311
|
${installed2.output}`);
|
|
1094
|
-
|
|
1312
|
+
writeFileSync6(paths.venvLock, `${lockDigest(lockFile)}
|
|
1095
1313
|
`);
|
|
1096
1314
|
}
|
|
1097
1315
|
|
|
@@ -1129,7 +1347,7 @@ function jsonOut(write) {
|
|
|
1129
1347
|
error: (message, kind) => emit({ type: "error", message, kind })
|
|
1130
1348
|
};
|
|
1131
1349
|
}
|
|
1132
|
-
function humanOut(write, tty) {
|
|
1350
|
+
function humanOut(write, tty, pkg = MUDE_PACKAGE) {
|
|
1133
1351
|
let shown = -1;
|
|
1134
1352
|
return {
|
|
1135
1353
|
step: (_step, message) => {
|
|
@@ -1153,7 +1371,7 @@ function humanOut(write, tty) {
|
|
|
1153
1371
|
},
|
|
1154
1372
|
line: (text) => write(` ${text}
|
|
1155
1373
|
`),
|
|
1156
|
-
status: (status2) => write(describe(status2)),
|
|
1374
|
+
status: (status2) => write(describe(status2, pkg)),
|
|
1157
1375
|
error: (message) => {
|
|
1158
1376
|
if (shown >= 0 && tty) write("\n");
|
|
1159
1377
|
shown = -1;
|
|
@@ -1162,31 +1380,43 @@ function humanOut(write, tty) {
|
|
|
1162
1380
|
}
|
|
1163
1381
|
};
|
|
1164
1382
|
}
|
|
1165
|
-
function describe(status2) {
|
|
1383
|
+
function describe(status2, pkg) {
|
|
1166
1384
|
const lines = [];
|
|
1167
1385
|
if (!status2.supported) {
|
|
1168
|
-
lines.push(
|
|
1386
|
+
lines.push(`${pkg.label} can't run here: ${status2.problems.join("; ")}`);
|
|
1169
1387
|
return `${lines.join("\n")}
|
|
1170
1388
|
`;
|
|
1171
1389
|
}
|
|
1172
1390
|
lines.push(
|
|
1173
|
-
`model ${status2.installed ? `installed (${
|
|
1391
|
+
`model ${status2.installed ? `installed (${pkg.repo} @ ${pkg.revision.slice(0, 7)})` : status2.downloadedBytes > 0 ? `partly downloaded (${(status2.downloadedBytes / 1e9).toFixed(1)} of ${(pkg.bytes / 1e9).toFixed(1)} GB): ${pkg.install} resumes it` : `not installed: ${pkg.install}`}`
|
|
1174
1392
|
);
|
|
1175
|
-
if (status2.installed && !status2.pythonReady) lines.push(
|
|
1393
|
+
if (status2.installed && !status2.pythonReady) lines.push(`python not set up: ${pkg.install}`);
|
|
1176
1394
|
lines.push(
|
|
1177
1395
|
`server ${status2.ready ? `running on 127.0.0.1:${status2.port}${status2.memoryBytes ? ` \xB7 ${(status2.memoryBytes / 1e9).toFixed(1)} GB of memory` : ""}` : status2.running ? `starting on 127.0.0.1:${status2.port}` : "stopped"}`
|
|
1178
1396
|
);
|
|
1179
1397
|
if (status2.enabled) {
|
|
1180
1398
|
lines.push(
|
|
1181
|
-
status2.localOnly ?
|
|
1399
|
+
status2.localOnly ? `judge ${pkg.label} only: a decision it cannot answer takes its fallback, and nothing goes to a hosted judge` : `judge ${pkg.label}, then ${status2.fallback.join(" > ")} for what it cannot answer (what those read leaves this machine)`
|
|
1182
1400
|
);
|
|
1183
1401
|
} else {
|
|
1184
|
-
lines.push(`judge ${status2.installed ?
|
|
1402
|
+
lines.push(`judge ${status2.installed ? `not enabled: ${pkg.again("enable")}` : "not enabled"}`);
|
|
1185
1403
|
}
|
|
1186
1404
|
lines.push(`folder ${status2.folder}`);
|
|
1187
1405
|
return `${lines.join("\n")}
|
|
1188
1406
|
`;
|
|
1189
1407
|
}
|
|
1408
|
+
var MUDE_PACKAGE = {
|
|
1409
|
+
label: "mude 1.0",
|
|
1410
|
+
repo: MUDE_REPO,
|
|
1411
|
+
revision: MUDE_REVISION,
|
|
1412
|
+
files: MUDE_FILES,
|
|
1413
|
+
bytes: MUDE_BYTES,
|
|
1414
|
+
judge: MUDE_JUDGE,
|
|
1415
|
+
served: MUDE_MODEL,
|
|
1416
|
+
install: "mu judge mude install",
|
|
1417
|
+
again: (what) => `mu judge mude ${what}`
|
|
1418
|
+
};
|
|
1419
|
+
var pkgOf = (ctx) => ctx.pkg ?? MUDE_PACKAGE;
|
|
1190
1420
|
function problems(options = {}) {
|
|
1191
1421
|
const platform = options.platform ?? process.platform;
|
|
1192
1422
|
if (platform !== "darwin") return ["it needs a Mac with Apple silicon (an M1 or newer)"];
|
|
@@ -1213,7 +1443,7 @@ function appleSilicon() {
|
|
|
1213
1443
|
function freeBytes(path) {
|
|
1214
1444
|
let dir = path;
|
|
1215
1445
|
while (!existsSync5(dir)) {
|
|
1216
|
-
const parent =
|
|
1446
|
+
const parent = join7(dir, "..");
|
|
1217
1447
|
if (parent === dir) return void 0;
|
|
1218
1448
|
dir = parent;
|
|
1219
1449
|
}
|
|
@@ -1229,7 +1459,7 @@ function installLocalOnly(ctx, args) {
|
|
|
1229
1459
|
if (args.includes("--local-only")) return true;
|
|
1230
1460
|
if (args.includes("--with-fallback")) return false;
|
|
1231
1461
|
try {
|
|
1232
|
-
const value = JSON.parse(
|
|
1462
|
+
const value = JSON.parse(readFileSync7(ctx.paths.install, "utf8"));
|
|
1233
1463
|
return typeof value === "object" && value !== null && "localOnly" in value && value.localOnly === true;
|
|
1234
1464
|
} catch {
|
|
1235
1465
|
return false;
|
|
@@ -1242,46 +1472,46 @@ function startedAt(pid) {
|
|
|
1242
1472
|
return void 0;
|
|
1243
1473
|
}
|
|
1244
1474
|
}
|
|
1245
|
-
function holdInstall(paths) {
|
|
1246
|
-
|
|
1475
|
+
function holdInstall(paths, pkg = MUDE_PACKAGE) {
|
|
1476
|
+
mkdirSync6(paths.root, { recursive: true });
|
|
1247
1477
|
const mine = `${JSON.stringify({ pid: process.pid, started: startedAt(process.pid) ?? "" })}
|
|
1248
1478
|
`;
|
|
1249
1479
|
try {
|
|
1250
|
-
|
|
1480
|
+
writeFileSync7(paths.installLock, mine, { flag: "wx" });
|
|
1251
1481
|
} catch (error) {
|
|
1252
1482
|
if (error.code !== "EEXIST") throw error;
|
|
1253
1483
|
let holder = {};
|
|
1254
1484
|
try {
|
|
1255
|
-
holder = JSON.parse(
|
|
1485
|
+
holder = JSON.parse(readFileSync7(paths.installLock, "utf8"));
|
|
1256
1486
|
} catch {
|
|
1257
1487
|
}
|
|
1258
1488
|
const pid = typeof holder.pid === "number" ? holder.pid : 0;
|
|
1259
1489
|
if (pid > 0 && pid !== process.pid && holder.started && startedAt(pid) === holder.started) {
|
|
1260
1490
|
throw new Error(
|
|
1261
|
-
`Another
|
|
1491
|
+
`Another ${pkg.label} install is running (process ${pid}). Wait for it to finish, or stop it, then run ${pkg.install} again`
|
|
1262
1492
|
);
|
|
1263
1493
|
}
|
|
1264
|
-
|
|
1494
|
+
writeFileSync7(paths.installLock, mine);
|
|
1265
1495
|
}
|
|
1266
1496
|
return () => {
|
|
1267
1497
|
try {
|
|
1268
|
-
if (
|
|
1498
|
+
if (readFileSync7(paths.installLock, "utf8") === mine) rmSync4(paths.installLock, { force: true });
|
|
1269
1499
|
} catch {
|
|
1270
1500
|
}
|
|
1271
1501
|
};
|
|
1272
1502
|
}
|
|
1273
1503
|
function installed(ctx) {
|
|
1274
|
-
return missingFiles(ctx.paths.model,
|
|
1504
|
+
return missingFiles(ctx.paths.model, pkgOf(ctx).files).length === 0;
|
|
1275
1505
|
}
|
|
1276
1506
|
function key(ctx) {
|
|
1277
1507
|
try {
|
|
1278
|
-
const existing =
|
|
1508
|
+
const existing = readFileSync7(ctx.paths.key, "utf8").trim();
|
|
1279
1509
|
if (existing) return existing;
|
|
1280
1510
|
} catch {
|
|
1281
1511
|
}
|
|
1282
|
-
|
|
1512
|
+
mkdirSync6(ctx.paths.root, { recursive: true });
|
|
1283
1513
|
const made = randomBytes(32).toString("hex");
|
|
1284
|
-
|
|
1514
|
+
writeFileSync7(ctx.paths.key, `${made}
|
|
1285
1515
|
`, { mode: 384 });
|
|
1286
1516
|
return made;
|
|
1287
1517
|
}
|
|
@@ -1291,18 +1521,20 @@ async function port(ctx) {
|
|
|
1291
1521
|
return choosePort(DEFAULT_PORT);
|
|
1292
1522
|
}
|
|
1293
1523
|
async function status(ctx) {
|
|
1524
|
+
const pkg = pkgOf(ctx);
|
|
1294
1525
|
const found = problems();
|
|
1295
1526
|
const state = readState(ctx.paths);
|
|
1296
1527
|
const at = state?.port ?? DEFAULT_PORT;
|
|
1297
1528
|
const pid = readPid(ctx.paths);
|
|
1298
1529
|
const running = serverAlive(ctx.paths, pid);
|
|
1299
1530
|
const ready = running && (await health(at))?.status === "ready";
|
|
1300
|
-
const setting = judgeSetting(ctx.files);
|
|
1531
|
+
const setting = judgeSetting(ctx.files, pkg.judge.name);
|
|
1301
1532
|
return {
|
|
1533
|
+
name: pkg.judge.name,
|
|
1302
1534
|
supported: found.length === 0,
|
|
1303
1535
|
problems: found,
|
|
1304
1536
|
installed: installed(ctx),
|
|
1305
|
-
downloadedBytes: downloadedBytes(ctx.paths.model,
|
|
1537
|
+
downloadedBytes: downloadedBytes(ctx.paths.model, pkg.files),
|
|
1306
1538
|
pythonReady: venvReady(ctx.paths, ctx.lockFile),
|
|
1307
1539
|
running,
|
|
1308
1540
|
ready,
|
|
@@ -1312,32 +1544,33 @@ async function status(ctx) {
|
|
|
1312
1544
|
enabled: setting.enabled,
|
|
1313
1545
|
localOnly: setting.localOnly,
|
|
1314
1546
|
fallback: setting.fallback,
|
|
1315
|
-
model: `${
|
|
1547
|
+
model: `${pkg.repo}@${pkg.revision}`,
|
|
1316
1548
|
folder: ctx.paths.root,
|
|
1317
1549
|
log: ctx.paths.log
|
|
1318
1550
|
};
|
|
1319
1551
|
}
|
|
1320
1552
|
async function ensureInstalled(ctx, out) {
|
|
1321
|
-
const
|
|
1553
|
+
const pkg = pkgOf(ctx);
|
|
1554
|
+
const missing = missingFiles(ctx.paths.model, pkg.files);
|
|
1322
1555
|
if (missing.length > 0) {
|
|
1323
1556
|
const need = missing.reduce((sum, file) => sum + file.size, 0);
|
|
1324
|
-
const already = downloadedBytes(ctx.paths.model,
|
|
1557
|
+
const already = downloadedBytes(ctx.paths.model, pkg.files);
|
|
1325
1558
|
const free = freeBytes(ctx.paths.root);
|
|
1326
|
-
const room =
|
|
1559
|
+
const room = pkg.bytes - already + 2e9;
|
|
1327
1560
|
if (free !== void 0 && free < room) {
|
|
1328
1561
|
throw new Error(
|
|
1329
|
-
`Not enough free disk space:
|
|
1562
|
+
`Not enough free disk space: ${pkg.label} needs about ${gb(room)} GB, and only ${gb(free)} GB are free. Free up some space, then run ${pkg.install} again`
|
|
1330
1563
|
);
|
|
1331
1564
|
}
|
|
1332
1565
|
out.step(
|
|
1333
1566
|
"download",
|
|
1334
|
-
already > 0 ? `Resuming the
|
|
1567
|
+
already > 0 ? `Resuming the ${pkg.label} download (${pkg.repo}): ${gb(already)} of ${gb(pkg.bytes)} GB already downloaded` : `Downloading ${pkg.label} (${pkg.repo}, ${gb(need)} GB) and checking every file`
|
|
1335
1568
|
);
|
|
1336
1569
|
try {
|
|
1337
1570
|
await downloadFiles({
|
|
1338
1571
|
dir: ctx.paths.model,
|
|
1339
|
-
files:
|
|
1340
|
-
url: (path) =>
|
|
1572
|
+
files: pkg.files,
|
|
1573
|
+
url: (path) => fileUrl(pkg.repo, pkg.revision, path, ctx.env.HF_ENDPOINT || void 0),
|
|
1341
1574
|
token: huggingFaceToken(ctx.env),
|
|
1342
1575
|
onProgress: (event) => out.progress(event),
|
|
1343
1576
|
signal: ctx.signal
|
|
@@ -1345,7 +1578,7 @@ async function ensureInstalled(ctx, out) {
|
|
|
1345
1578
|
} catch (error) {
|
|
1346
1579
|
if (error instanceof DownloadError && error.kind === "aborted") {
|
|
1347
1580
|
throw new DownloadError(
|
|
1348
|
-
`Stopped with ${gb(downloadedBytes(ctx.paths.model,
|
|
1581
|
+
`Stopped with ${gb(downloadedBytes(ctx.paths.model, pkg.files))} of ${gb(pkg.bytes)} GB downloaded. Run ${pkg.install} to resume`,
|
|
1349
1582
|
"aborted"
|
|
1350
1583
|
);
|
|
1351
1584
|
}
|
|
@@ -1353,7 +1586,7 @@ async function ensureInstalled(ctx, out) {
|
|
|
1353
1586
|
}
|
|
1354
1587
|
}
|
|
1355
1588
|
if (!venvReady(ctx.paths, ctx.lockFile)) {
|
|
1356
|
-
out.step("python",
|
|
1589
|
+
out.step("python", `Setting up the Python environment for ${pkg.label} (MLX, transformers; about 1 GB, once)`);
|
|
1357
1590
|
try {
|
|
1358
1591
|
let uv = findUv(ctx.paths, ctx.env);
|
|
1359
1592
|
if (!uv) uv = await downloadUv(ctx.paths, { onLine: (line) => out.line(line), signal: ctx.signal });
|
|
@@ -1361,7 +1594,7 @@ async function ensureInstalled(ctx, out) {
|
|
|
1361
1594
|
} catch (error) {
|
|
1362
1595
|
if (ctx.signal?.aborted) {
|
|
1363
1596
|
throw new DownloadError(
|
|
1364
|
-
|
|
1597
|
+
`Stopped while setting up the Python environment. Run ${pkg.install} to resume`,
|
|
1365
1598
|
"aborted"
|
|
1366
1599
|
);
|
|
1367
1600
|
}
|
|
@@ -1371,21 +1604,23 @@ async function ensureInstalled(ctx, out) {
|
|
|
1371
1604
|
}
|
|
1372
1605
|
function requireInstalled(ctx) {
|
|
1373
1606
|
if (!installed(ctx) || !venvReady(ctx.paths, ctx.lockFile)) {
|
|
1374
|
-
|
|
1607
|
+
const pkg = pkgOf(ctx);
|
|
1608
|
+
throw new Error(`${pkg.label} is not installed here yet: ${pkg.install}`);
|
|
1375
1609
|
}
|
|
1376
1610
|
}
|
|
1377
1611
|
async function start(ctx, out, command, wait2 = true) {
|
|
1612
|
+
const pkg = pkgOf(ctx);
|
|
1378
1613
|
const at = await port(ctx);
|
|
1379
1614
|
out.step(
|
|
1380
1615
|
"start",
|
|
1381
|
-
serverAlive(ctx.paths) ? `The
|
|
1616
|
+
serverAlive(ctx.paths) ? `The ${pkg.label} server is running on 127.0.0.1:${at}` : `Starting the ${pkg.label} server on 127.0.0.1:${at} (loading the model takes a few seconds)`
|
|
1382
1617
|
);
|
|
1383
1618
|
try {
|
|
1384
|
-
await startServer(ctx.paths, { port: at, key: key(ctx), wait: wait2, signal: ctx.signal });
|
|
1619
|
+
await startServer(ctx.paths, { port: at, key: key(ctx), wait: wait2, signal: ctx.signal, name: pkg.served });
|
|
1385
1620
|
} catch (error) {
|
|
1386
1621
|
if (error instanceof StartStopped) {
|
|
1387
1622
|
throw new DownloadError(
|
|
1388
|
-
error.serverStopped ? `Stopped while the
|
|
1623
|
+
error.serverStopped ? `Stopped while the ${pkg.label} server was starting, and stopped the server too. Run ${pkg.again(command)} again to finish` : `Stopped waiting for the ${pkg.label} server, which is still starting. Run ${pkg.again(command)} again to finish`,
|
|
1389
1624
|
"aborted"
|
|
1390
1625
|
);
|
|
1391
1626
|
}
|
|
@@ -1393,16 +1628,60 @@ async function start(ctx, out, command, wait2 = true) {
|
|
|
1393
1628
|
}
|
|
1394
1629
|
return at;
|
|
1395
1630
|
}
|
|
1396
|
-
function
|
|
1397
|
-
const
|
|
1631
|
+
function others(ctx) {
|
|
1632
|
+
const name = pkgOf(ctx).judge.name;
|
|
1633
|
+
const home = ctx.files.home;
|
|
1634
|
+
const found = [];
|
|
1635
|
+
if (name !== MUDE) found.push({ judge: MUDE_JUDGE, paths: mudePaths(home) });
|
|
1636
|
+
for (const record of listRecords(home)) {
|
|
1637
|
+
if (record.id !== name) found.push({ judge: localJudgeOf(record.id), paths: modelPaths(home, record) });
|
|
1638
|
+
}
|
|
1639
|
+
return found;
|
|
1640
|
+
}
|
|
1641
|
+
async function stopOthers(ctx) {
|
|
1642
|
+
for (const other of others(ctx)) await stopServer(other.paths);
|
|
1643
|
+
}
|
|
1644
|
+
async function enable(ctx, out, at, localOnly) {
|
|
1645
|
+
const pkg = pkgOf(ctx);
|
|
1646
|
+
const tiers = currentTiers(ctx.files);
|
|
1647
|
+
for (const other of others(ctx)) {
|
|
1648
|
+
if (tiers.includes(other.judge.name)) disableLocal(other.judge, other.paths, ctx.files);
|
|
1649
|
+
}
|
|
1650
|
+
const order = enableLocal(pkg.judge, ctx.paths, ctx.files, { port: at, key: key(ctx), localOnly });
|
|
1398
1651
|
out.step(
|
|
1399
1652
|
"enable",
|
|
1400
|
-
localOnly ?
|
|
1653
|
+
localOnly ? `${pkg.label} is the judge, on its own. New mu sessions use it; one that is open now picks it up when it starts again.` : `${pkg.label} is the judge, with ${order.slice(1).join(" > ")} behind it for what it cannot answer. New mu sessions use it.`
|
|
1401
1654
|
);
|
|
1402
1655
|
}
|
|
1403
|
-
async function
|
|
1404
|
-
const
|
|
1405
|
-
const
|
|
1656
|
+
async function install(ctx, out, args, holdAlso) {
|
|
1657
|
+
const localOnly = installLocalOnly(ctx, args);
|
|
1658
|
+
const release2 = holdInstall(ctx.paths, pkgOf(ctx));
|
|
1659
|
+
let releaseAlso;
|
|
1660
|
+
try {
|
|
1661
|
+
if (holdAlso && !venvReady(ctx.paths, ctx.lockFile)) releaseAlso = holdInstall(holdAlso);
|
|
1662
|
+
writeFileSync7(ctx.paths.install, `${JSON.stringify({ localOnly })}
|
|
1663
|
+
`);
|
|
1664
|
+
await ensureInstalled(ctx, out);
|
|
1665
|
+
await stopOthers(ctx);
|
|
1666
|
+
const at = await start(ctx, out, "install");
|
|
1667
|
+
await enable(ctx, out, at, localOnly);
|
|
1668
|
+
rmSync4(ctx.paths.install, { force: true });
|
|
1669
|
+
} finally {
|
|
1670
|
+
releaseAlso?.();
|
|
1671
|
+
release2();
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
async function enableCommand(ctx, out, args) {
|
|
1675
|
+
requireInstalled(ctx);
|
|
1676
|
+
const current = judgeSetting(ctx.files, pkgOf(ctx).judge.name);
|
|
1677
|
+
const localOnly = args.includes("--local-only") ? true : args.includes("--with-fallback") ? false : current.enabled && current.localOnly;
|
|
1678
|
+
await stopOthers(ctx);
|
|
1679
|
+
const at = await start(ctx, out, "enable");
|
|
1680
|
+
await enable(ctx, out, at, localOnly);
|
|
1681
|
+
}
|
|
1682
|
+
async function main(argv2, ctx, write, tty = false) {
|
|
1683
|
+
const json = argv2.includes("--json");
|
|
1684
|
+
const args = argv2.filter((arg) => arg !== "--json");
|
|
1406
1685
|
const out = json ? jsonOut(write) : humanOut(write, tty);
|
|
1407
1686
|
const command = args[0] ?? "status";
|
|
1408
1687
|
const flag = (name) => args.includes(name);
|
|
@@ -1426,29 +1705,12 @@ async function main(argv, ctx, write, tty = false) {
|
|
|
1426
1705
|
return 1;
|
|
1427
1706
|
}
|
|
1428
1707
|
switch (command) {
|
|
1429
|
-
case "install":
|
|
1430
|
-
|
|
1431
|
-
const release2 = holdInstall(ctx.paths);
|
|
1432
|
-
try {
|
|
1433
|
-
writeFileSync6(ctx.paths.install, `${JSON.stringify({ localOnly })}
|
|
1434
|
-
`);
|
|
1435
|
-
await ensureInstalled(ctx, out);
|
|
1436
|
-
const at = await start(ctx, out, "install");
|
|
1437
|
-
enable(ctx, out, at, localOnly);
|
|
1438
|
-
rmSync4(ctx.paths.install, { force: true });
|
|
1439
|
-
} finally {
|
|
1440
|
-
release2();
|
|
1441
|
-
}
|
|
1708
|
+
case "install":
|
|
1709
|
+
await install(ctx, out, args);
|
|
1442
1710
|
break;
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
requireInstalled(ctx);
|
|
1446
|
-
const current = judgeSetting(ctx.files);
|
|
1447
|
-
const localOnly = flag("--local-only") ? true : flag("--with-fallback") ? false : current.enabled && current.localOnly;
|
|
1448
|
-
const at = await start(ctx, out, "enable");
|
|
1449
|
-
enable(ctx, out, at, localOnly);
|
|
1711
|
+
case "enable":
|
|
1712
|
+
await enableCommand(ctx, out, args);
|
|
1450
1713
|
break;
|
|
1451
|
-
}
|
|
1452
1714
|
case "disable": {
|
|
1453
1715
|
if (!mudeInTiers(ctx.files)) {
|
|
1454
1716
|
const stopped2 = await stopServer(ctx.paths);
|
|
@@ -1479,8 +1741,20 @@ async function main(argv, ctx, write, tty = false) {
|
|
|
1479
1741
|
out.step("disable", `mude 1.0 is no longer the judge: ${tiers.join(" > ")} again.`);
|
|
1480
1742
|
}
|
|
1481
1743
|
await stopServer(ctx.paths);
|
|
1482
|
-
|
|
1483
|
-
|
|
1744
|
+
if (listRecords(ctx.files.home).length > 0) {
|
|
1745
|
+
for (const name of existsSync5(ctx.paths.root) ? readdirSync2(ctx.paths.root) : []) {
|
|
1746
|
+
const path = join7(ctx.paths.root, name);
|
|
1747
|
+
if (path !== ctx.paths.venv && path !== ctx.paths.tools)
|
|
1748
|
+
rmSync4(path, { recursive: true, force: true });
|
|
1749
|
+
}
|
|
1750
|
+
out.step(
|
|
1751
|
+
"uninstall",
|
|
1752
|
+
`Deleted mude 1.0 and its key from ${ctx.paths.root}. Its Python environment stays there for the other decision models.`
|
|
1753
|
+
);
|
|
1754
|
+
} else {
|
|
1755
|
+
rmSync4(ctx.paths.root, { recursive: true, force: true });
|
|
1756
|
+
out.step("uninstall", `Deleted ${ctx.paths.root}: the model, its Python environment and its key.`);
|
|
1757
|
+
}
|
|
1484
1758
|
break;
|
|
1485
1759
|
}
|
|
1486
1760
|
default:
|
|
@@ -1498,16 +1772,245 @@ ${USAGE}`, "usage");
|
|
|
1498
1772
|
}
|
|
1499
1773
|
}
|
|
1500
1774
|
function contextFromEnv(env = process.env, home = homedir3()) {
|
|
1501
|
-
const muHome = env.MU_MUDE_HOME ||
|
|
1502
|
-
const agentDir = env.MU_AGENT_DIR ||
|
|
1775
|
+
const muHome = env.MU_MUDE_HOME || join7(home, ".mu");
|
|
1776
|
+
const agentDir = env.MU_AGENT_DIR || join7(muHome, "agent");
|
|
1503
1777
|
return {
|
|
1504
1778
|
paths: mudePaths(muHome),
|
|
1505
|
-
files: { agentDir, home: muHome, envFile: env.MU_MUDE_ENV_FILE ||
|
|
1506
|
-
lockFile: env.MU_MUDE_LOCK ||
|
|
1779
|
+
files: { agentDir, home: muHome, envFile: env.MU_MUDE_ENV_FILE || join7(muHome, ".env") },
|
|
1780
|
+
lockFile: env.MU_MUDE_LOCK || join7(import.meta.dirname, "..", "..", "..", "..", "kyrn", "local-judge", "mude-requirements.lock.txt"),
|
|
1507
1781
|
env
|
|
1508
1782
|
};
|
|
1509
1783
|
}
|
|
1510
1784
|
|
|
1785
|
+
// packages/kyrn-judge/src/mude/models-command.ts
|
|
1786
|
+
import { rmSync as rmSync5 } from "node:fs";
|
|
1787
|
+
var USAGE2 = `mu judge model: decision models from Hugging Face, run on this Mac like mude 1.0
|
|
1788
|
+
|
|
1789
|
+
mu judge model add <link or owner/name>[@revision] [--local-only]
|
|
1790
|
+
download it, set it up, start it and make it the judge; run it again to resume
|
|
1791
|
+
mu judge model list mude 1.0 and the models added here
|
|
1792
|
+
mu judge model use <name> [--local-only | --with-fallback]
|
|
1793
|
+
make it the judge (mude for mude 1.0)
|
|
1794
|
+
mu judge model start | stop | status | disable <name>
|
|
1795
|
+
mu judge model remove <name>
|
|
1796
|
+
stop it, take it out of the judge order and delete it
|
|
1797
|
+
|
|
1798
|
+
mu runs a repository packaged like mude 1.0's MLX edition, with its own ${SERVER_FILE}. Add --json for one JSON object per line.
|
|
1799
|
+
`;
|
|
1800
|
+
function packageOf(record) {
|
|
1801
|
+
const command = (what) => what === "install" ? "add" : what === "enable" ? "use" : what;
|
|
1802
|
+
return {
|
|
1803
|
+
label: record.id,
|
|
1804
|
+
repo: record.repo,
|
|
1805
|
+
revision: record.revision,
|
|
1806
|
+
files: record.files,
|
|
1807
|
+
bytes: record.bytes,
|
|
1808
|
+
judge: localJudgeOf(record.id),
|
|
1809
|
+
served: record.id,
|
|
1810
|
+
install: `mu judge model add ${record.id}`,
|
|
1811
|
+
again: (what) => `mu judge model ${command(what)} ${record.id}`
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
function modelContext(ctx, record) {
|
|
1815
|
+
return { ...ctx, paths: modelPaths(ctx.files.home, record), pkg: packageOf(record) };
|
|
1816
|
+
}
|
|
1817
|
+
function summary(pkg, state, builtIn) {
|
|
1818
|
+
return {
|
|
1819
|
+
name: pkg.judge.name,
|
|
1820
|
+
label: pkg.label,
|
|
1821
|
+
repo: pkg.repo,
|
|
1822
|
+
revision: pkg.revision,
|
|
1823
|
+
bytes: pkg.bytes,
|
|
1824
|
+
builtIn,
|
|
1825
|
+
installed: state.installed && state.pythonReady,
|
|
1826
|
+
downloadedBytes: state.downloadedBytes,
|
|
1827
|
+
running: state.running,
|
|
1828
|
+
ready: state.ready,
|
|
1829
|
+
enabled: state.enabled,
|
|
1830
|
+
localOnly: state.localOnly
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
async function listModels(ctx) {
|
|
1834
|
+
const mude = { ...ctx, paths: mudePaths(ctx.files.home), pkg: void 0 };
|
|
1835
|
+
const models = [summary(MUDE_PACKAGE, await status(mude), true)];
|
|
1836
|
+
for (const record of listRecords(ctx.files.home)) {
|
|
1837
|
+
const each = modelContext(ctx, record);
|
|
1838
|
+
models.push(summary(packageOf(record), await status(each), false));
|
|
1839
|
+
}
|
|
1840
|
+
return models;
|
|
1841
|
+
}
|
|
1842
|
+
var gb2 = (bytes) => (bytes / 1e9).toFixed(1);
|
|
1843
|
+
function describeList(models) {
|
|
1844
|
+
const width = Math.max(...models.map((model) => model.name.length));
|
|
1845
|
+
const lines = models.map((model) => {
|
|
1846
|
+
const state = model.installed ? model.ready ? "running" : model.running ? "starting" : "installed" : model.downloadedBytes > 0 ? `${gb2(model.downloadedBytes)} of ${gb2(model.bytes)} GB downloaded` : model.builtIn ? "not installed" : "not downloaded";
|
|
1847
|
+
const judge = model.enabled ? model.localOnly ? " \xB7 the judge, on its own" : " \xB7 the judge" : "";
|
|
1848
|
+
return `${model.name.padEnd(width)} ${model.repo} @ ${model.revision.slice(0, 7)} \xB7 ${gb2(model.bytes)} GB \xB7 ${state}${judge}`;
|
|
1849
|
+
});
|
|
1850
|
+
return `${lines.join("\n")}
|
|
1851
|
+
`;
|
|
1852
|
+
}
|
|
1853
|
+
function isMude(name) {
|
|
1854
|
+
if (name.toLowerCase() === MUDE) return true;
|
|
1855
|
+
try {
|
|
1856
|
+
return parseModelRef(name).repo.toLowerCase() === MUDE_REPO.toLowerCase();
|
|
1857
|
+
} catch {
|
|
1858
|
+
return false;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
async function addRecord(ctx, name, out, options) {
|
|
1862
|
+
const home = ctx.files.home;
|
|
1863
|
+
const known = findRecord(home, name);
|
|
1864
|
+
if (known) return known;
|
|
1865
|
+
const ref = parseModelRef(name);
|
|
1866
|
+
const existing = findRecord(home, ref.repo);
|
|
1867
|
+
if (existing && ref.revision === void 0) return existing;
|
|
1868
|
+
out.step("resolve", `Looking up ${ref.repo}${ref.revision ? ` at ${ref.revision}` : ""} on Hugging Face`);
|
|
1869
|
+
const resolved = await resolveModel(ref, {
|
|
1870
|
+
endpoint: ctx.env.HF_ENDPOINT || void 0,
|
|
1871
|
+
token: huggingFaceToken(ctx.env),
|
|
1872
|
+
fetch: options.fetch
|
|
1873
|
+
});
|
|
1874
|
+
const id = existing?.id ?? modelId(ref.repo, new Set(listRecords(home).map((record2) => record2.id)));
|
|
1875
|
+
if (existing && existing.revision !== resolved.revision) {
|
|
1876
|
+
rmSync5(modelPaths(home, existing).model, { recursive: true, force: true });
|
|
1877
|
+
}
|
|
1878
|
+
const record = {
|
|
1879
|
+
id,
|
|
1880
|
+
repo: ref.repo,
|
|
1881
|
+
revision: resolved.revision,
|
|
1882
|
+
files: resolved.files,
|
|
1883
|
+
bytes: resolved.files.reduce((sum, file) => sum + file.size, 0)
|
|
1884
|
+
};
|
|
1885
|
+
writeRecord(home, record);
|
|
1886
|
+
return record;
|
|
1887
|
+
}
|
|
1888
|
+
async function modelMain(argv2, ctx, write, tty = false, options = {}) {
|
|
1889
|
+
const json = argv2.includes("--json");
|
|
1890
|
+
const args = argv2.filter((arg) => arg !== "--json");
|
|
1891
|
+
let out = json ? jsonOut(write) : humanOut(write, tty);
|
|
1892
|
+
const command = args[0] ?? "list";
|
|
1893
|
+
const names = args.slice(1).filter((arg) => !arg.startsWith("--"));
|
|
1894
|
+
const flags = args.slice(1).filter((arg) => arg.startsWith("--"));
|
|
1895
|
+
try {
|
|
1896
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
1897
|
+
write(USAGE2);
|
|
1898
|
+
return 0;
|
|
1899
|
+
}
|
|
1900
|
+
const unknown = flags.find((flag) => !["--local-only", "--with-fallback", "--no-wait"].includes(flag));
|
|
1901
|
+
if (unknown) {
|
|
1902
|
+
out.error(`Unknown option for mu judge model ${command}: ${unknown} (mu judge model help)`, "usage");
|
|
1903
|
+
return 2;
|
|
1904
|
+
}
|
|
1905
|
+
if (command === "list") {
|
|
1906
|
+
const models = await listModels(ctx);
|
|
1907
|
+
if (json) write(`${JSON.stringify({ type: "models", models })}
|
|
1908
|
+
`);
|
|
1909
|
+
else write(describeList(models));
|
|
1910
|
+
return 0;
|
|
1911
|
+
}
|
|
1912
|
+
if (!["add", "use", "start", "stop", "status", "disable", "remove"].includes(command)) {
|
|
1913
|
+
out.error(`Unknown command: mu judge model ${command}
|
|
1914
|
+
|
|
1915
|
+
${USAGE2}`, "usage");
|
|
1916
|
+
return 2;
|
|
1917
|
+
}
|
|
1918
|
+
const name = names[0];
|
|
1919
|
+
if (!name || names.length > 1) {
|
|
1920
|
+
out.error(
|
|
1921
|
+
`Name one model: mu judge model ${command} <${command === "add" ? "link or owner/name" : "name"}>`,
|
|
1922
|
+
"usage"
|
|
1923
|
+
);
|
|
1924
|
+
return 2;
|
|
1925
|
+
}
|
|
1926
|
+
if (isMude(name)) {
|
|
1927
|
+
const as = { add: "install", use: "enable", remove: "uninstall" }[command] ?? command;
|
|
1928
|
+
return main([as, ...flags, ...json ? ["--json"] : []], ctx, write, tty);
|
|
1929
|
+
}
|
|
1930
|
+
const home = ctx.files.home;
|
|
1931
|
+
const unsupported = problems();
|
|
1932
|
+
if (command === "add") {
|
|
1933
|
+
if (unsupported.length > 0) {
|
|
1934
|
+
out.error(`Decision models can't run on this machine: ${unsupported.join("; ")}.`, "unsupported");
|
|
1935
|
+
return 1;
|
|
1936
|
+
}
|
|
1937
|
+
const record2 = await addRecord(ctx, name, out, options);
|
|
1938
|
+
const each2 = modelContext(ctx, record2);
|
|
1939
|
+
if (json)
|
|
1940
|
+
write(
|
|
1941
|
+
`${JSON.stringify({ type: "model", name: record2.id, repo: record2.repo, revision: record2.revision, bytes: record2.bytes })}
|
|
1942
|
+
`
|
|
1943
|
+
);
|
|
1944
|
+
else out = humanOut(write, tty, each2.pkg);
|
|
1945
|
+
out.step(
|
|
1946
|
+
"model",
|
|
1947
|
+
`${record2.repo} @ ${record2.revision.slice(0, 7)} (${gb2(record2.bytes)} GB) is the decision model ${record2.id}. It runs its own server code (${SERVER_FILE}) on this Mac: add only models you trust.`
|
|
1948
|
+
);
|
|
1949
|
+
await install(each2, out, flags, mudePaths(home));
|
|
1950
|
+
out.status(await status(each2));
|
|
1951
|
+
return 0;
|
|
1952
|
+
}
|
|
1953
|
+
const record = findRecord(home, name);
|
|
1954
|
+
if (!record) {
|
|
1955
|
+
out.error(
|
|
1956
|
+
`No decision model ${name} here (mu judge model list). Add it: mu judge model add <link or owner/name>`,
|
|
1957
|
+
"usage"
|
|
1958
|
+
);
|
|
1959
|
+
return 1;
|
|
1960
|
+
}
|
|
1961
|
+
const each = modelContext(ctx, record);
|
|
1962
|
+
const pkg = packageOf(record);
|
|
1963
|
+
if (!json) out = humanOut(write, tty, pkg);
|
|
1964
|
+
if (unsupported.length > 0 && (command === "use" || command === "start")) {
|
|
1965
|
+
out.error(`${record.id} can't run on this machine: ${unsupported.join("; ")}.`, "unsupported");
|
|
1966
|
+
return 1;
|
|
1967
|
+
}
|
|
1968
|
+
switch (command) {
|
|
1969
|
+
case "use":
|
|
1970
|
+
await enableCommand(each, out, flags);
|
|
1971
|
+
break;
|
|
1972
|
+
case "start":
|
|
1973
|
+
requireInstalled(each);
|
|
1974
|
+
await start(each, out, "start", !flags.includes("--no-wait"));
|
|
1975
|
+
break;
|
|
1976
|
+
case "stop": {
|
|
1977
|
+
const stopped = await stopServer(each.paths);
|
|
1978
|
+
out.step(
|
|
1979
|
+
"stop",
|
|
1980
|
+
stopped ? `The ${record.id} server is stopped.` : `The ${record.id} server was not running.`
|
|
1981
|
+
);
|
|
1982
|
+
break;
|
|
1983
|
+
}
|
|
1984
|
+
case "disable": {
|
|
1985
|
+
const was = mudeInTiers(ctx.files, record.id);
|
|
1986
|
+
const tiers = was ? disableLocal(pkg.judge, each.paths, ctx.files) : [];
|
|
1987
|
+
const stopped = await stopServer(each.paths);
|
|
1988
|
+
out.step(
|
|
1989
|
+
"disable",
|
|
1990
|
+
was ? `${record.id} is no longer the judge: ${tiers.join(" > ")} again${stopped ? ", and its server is stopped" : ""}.` : `${record.id} was not the judge${stopped ? "; its server is stopped" : ""}.`
|
|
1991
|
+
);
|
|
1992
|
+
break;
|
|
1993
|
+
}
|
|
1994
|
+
case "remove": {
|
|
1995
|
+
if (mudeInTiers(ctx.files, record.id) || readState(each.paths)?.wroteEntry) {
|
|
1996
|
+
const tiers = disableLocal(pkg.judge, each.paths, ctx.files, { removeEntry: true });
|
|
1997
|
+
out.step("disable", `${record.id} is no longer the judge: ${tiers.join(" > ")} again.`);
|
|
1998
|
+
}
|
|
1999
|
+
await stopServer(each.paths);
|
|
2000
|
+
rmSync5(each.paths.root, { recursive: true, force: true });
|
|
2001
|
+
out.step("remove", `Deleted ${each.paths.root}: the model and its key.`);
|
|
2002
|
+
return 0;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
out.status(await status(each));
|
|
2006
|
+
return 0;
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
const kind = error instanceof DownloadError || error instanceof ModelError ? error.kind : void 0;
|
|
2009
|
+
out.error(error instanceof Error ? error.message : String(error), kind);
|
|
2010
|
+
return 1;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
|
|
1511
2014
|
// packages/kyrn-judge/src/mude/cli.ts
|
|
1512
2015
|
var stop = new AbortController();
|
|
1513
2016
|
var onSignal = (signal) => {
|
|
@@ -1517,8 +2020,10 @@ var onSignal = (signal) => {
|
|
|
1517
2020
|
};
|
|
1518
2021
|
process.on("SIGINT", onSignal);
|
|
1519
2022
|
process.on("SIGTERM", onSignal);
|
|
1520
|
-
|
|
1521
|
-
|
|
2023
|
+
var argv = process.argv.slice(2);
|
|
2024
|
+
var run2 = argv[0] === "model" ? modelMain : main;
|
|
2025
|
+
run2(
|
|
2026
|
+
argv[0] === "model" ? argv.slice(1) : argv,
|
|
1522
2027
|
{ ...contextFromEnv(), signal: stop.signal },
|
|
1523
2028
|
(text) => process.stdout.write(text),
|
|
1524
2029
|
Boolean(process.stdout.isTTY)
|
|
@@ -1527,8 +2032,10 @@ main(
|
|
|
1527
2032
|
process.exitCode = code;
|
|
1528
2033
|
},
|
|
1529
2034
|
(error) => {
|
|
1530
|
-
process.stderr.write(
|
|
1531
|
-
`)
|
|
2035
|
+
process.stderr.write(
|
|
2036
|
+
`mu judge ${argv[0] === "model" ? "model" : "mude"}: ${error instanceof Error ? error.message : String(error)}
|
|
2037
|
+
`
|
|
2038
|
+
);
|
|
1532
2039
|
process.exitCode = 1;
|
|
1533
2040
|
}
|
|
1534
2041
|
);
|