






















@@ -41,6 +41,10 @@ const ensureSupportedNodeVersion = () => {
41414242ensureSupportedNodeVersion();
434344+if (tryOutputLauncherVersion(process.argv)) {
45+process.exit(0);
46+}
47+4448const isSourceCheckoutLauncher = () =>
4549existsSync(new URL("./.git", import.meta.url)) ||
4650existsSync(new URL("./src/entry.ts", import.meta.url));
@@ -445,6 +449,155 @@ const loadPrecomputedHelpText = (key) => {
445449}
446450};
447451452+function tryOutputLauncherVersion(argv) {
453+try {
454+if (normalizeLauncherMetadataValue(process.env.OPENCLAW_CONTAINER)) {
455+return false;
456+}
457+if (!isLauncherVersionFastPathArgv(argv)) {
458+return false;
459+}
460+const version = resolveLauncherVersion();
461+const commit = resolveLauncherCommit();
462+process.stdout.write(commit ? `OpenClaw ${version} (${commit})\n` : `OpenClaw ${version}\n`);
463+return true;
464+} catch {
465+return false;
466+}
467+}
468+469+function isLauncherVersionFastPathArgv(argv) {
470+return argv.length === 3 && (argv[2] === "--version" || argv[2] === "-V" || argv[2] === "-v");
471+}
472+473+function normalizeLauncherMetadataValue(value) {
474+const trimmed = typeof value === "string" ? value.trim() : "";
475+return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : undefined;
476+}
477+478+function readLauncherJson(relativePath) {
479+try {
480+return JSON.parse(readFileSync(new URL(relativePath, import.meta.url), "utf8"));
481+} catch {
482+return null;
483+}
484+}
485+486+function resolveLauncherVersion() {
487+const packageJson = readLauncherJson("./package.json");
488+const packageVersion = normalizeLauncherMetadataValue(packageJson?.version);
489+if (packageVersion) {
490+return packageVersion;
491+}
492+const buildInfo = readLauncherJson("./dist/build-info.json");
493+const buildVersion = normalizeLauncherMetadataValue(buildInfo?.version);
494+if (buildVersion) {
495+return buildVersion;
496+}
497+return normalizeLauncherMetadataValue(process.env.OPENCLAW_BUNDLED_VERSION) ?? "0.0.0";
498+}
499+500+function resolveLauncherCommit() {
501+const envCommit = formatLauncherCommit(process.env.GIT_COMMIT ?? process.env.GIT_SHA);
502+if (envCommit) {
503+return envCommit;
504+}
505+return (
506+readLauncherGitCommit() ??
507+formatLauncherCommit(readLauncherJson("./dist/build-info.json")?.commit) ??
508+formatLauncherCommit(readLauncherJson("./package.json")?.gitHead) ??
509+formatLauncherCommit(readLauncherJson("./package.json")?.githead)
510+);
511+}
512+513+function formatLauncherCommit(value) {
514+if (typeof value !== "string") {
515+return null;
516+}
517+const match = value.trim().match(/[0-9a-fA-F]{7,40}/);
518+return match ? match[0].slice(0, 7).toLowerCase() : null;
519+}
520+521+function readLauncherGitCommit() {
522+try {
523+const gitPath = fileURLToPath(new URL("./.git", import.meta.url));
524+const headPath = resolveLauncherGitHeadPath(gitPath);
525+if (!headPath) {
526+return null;
527+}
528+const head = readFileSync(headPath, "utf8").trim();
529+if (!head) {
530+return null;
531+}
532+if (!head.startsWith("ref:")) {
533+return formatLauncherCommit(head);
534+}
535+const ref = head.replace(/^ref:\s*/i, "").trim();
536+if (!ref.startsWith("refs/") || path.isAbsolute(ref) || ref.split("/").includes("..")) {
537+return null;
538+}
539+const refsBase = resolveLauncherGitRefsBase(headPath);
540+const refPath = path.resolve(refsBase, ref);
541+const rel = path.relative(refsBase, refPath);
542+if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
543+return null;
544+}
545+try {
546+return formatLauncherCommit(readFileSync(refPath, "utf8"));
547+} catch {
548+return readLauncherPackedRef(refsBase, ref);
549+}
550+} catch {
551+return null;
552+}
553+}
554+555+function resolveLauncherGitHeadPath(gitPath) {
556+try {
557+if (statSync(gitPath).isDirectory()) {
558+return path.join(gitPath, "HEAD");
559+}
560+const raw = readFileSync(gitPath, "utf8").trim();
561+if (!raw.startsWith("gitdir:")) {
562+return null;
563+}
564+return path.join(
565+path.resolve(path.dirname(gitPath), raw.slice("gitdir:".length).trim()),
566+"HEAD",
567+);
568+} catch {
569+return null;
570+}
571+}
572+573+function resolveLauncherGitRefsBase(headPath) {
574+const gitDir = path.dirname(headPath);
575+try {
576+const commonDir = readFileSync(path.join(gitDir, "commondir"), "utf8").trim();
577+return commonDir ? path.resolve(gitDir, commonDir) : gitDir;
578+} catch {
579+return gitDir;
580+}
581+}
582+583+function readLauncherPackedRef(refsBase, ref) {
584+try {
585+const packedRefs = readFileSync(path.join(refsBase, "packed-refs"), "utf8");
586+for (const line of packedRefs.split("\n")) {
587+if (!line || line.startsWith("#") || line.startsWith("^")) {
588+continue;
589+}
590+const [commit, packedRef] = line.trim().split(/\s+/, 2);
591+if (packedRef === ref) {
592+return formatLauncherCommit(commit);
593+}
594+}
595+} catch {
596+// fall through
597+}
598+return null;
599+}
600+448601const tryOutputBareRootHelp = async () => {
449602if (!isBareRootHelpInvocation(process.argv)) {
450603return false;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。