clawmarketbot 0.1.0 → 0.1.2
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 +383 -268
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -48,6 +48,17 @@ async function askSecret(question) {
|
|
|
48
48
|
const value = await password({ message: question.trim() || "Secret", mask: true });
|
|
49
49
|
return value.trim();
|
|
50
50
|
}
|
|
51
|
+
async function askOptional(question, hintUrl) {
|
|
52
|
+
if (hintUrl) {
|
|
53
|
+
console.log(` Get one at: ${hintUrl}`);
|
|
54
|
+
}
|
|
55
|
+
const value = await input({ message: `${question.trim()} (Enter to skip)`, default: "" });
|
|
56
|
+
const trimmed = value.trim();
|
|
57
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
58
|
+
}
|
|
59
|
+
async function confirm(question, defaultYes = true) {
|
|
60
|
+
return promptConfirm({ message: question, default: defaultYes });
|
|
61
|
+
}
|
|
51
62
|
|
|
52
63
|
// src/lib/auth-config.ts
|
|
53
64
|
import path from "path";
|
|
@@ -201,13 +212,13 @@ Hey ${username}! You're now logged in.
|
|
|
201
212
|
}
|
|
202
213
|
|
|
203
214
|
// src/commands/config.ts
|
|
204
|
-
import
|
|
205
|
-
import
|
|
215
|
+
import fs4 from "fs/promises";
|
|
216
|
+
import path3 from "path";
|
|
206
217
|
import os3 from "os";
|
|
207
|
-
import { createHash
|
|
208
|
-
import { spawn
|
|
218
|
+
import { createHash, randomUUID } from "crypto";
|
|
219
|
+
import { spawn } from "child_process";
|
|
209
220
|
import JSON52 from "json5";
|
|
210
|
-
import
|
|
221
|
+
import fetch2 from "node-fetch";
|
|
211
222
|
|
|
212
223
|
// ../../contracts/token.js
|
|
213
224
|
var CLAWMARKET_DOWNLOAD_TOKEN_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
|
|
@@ -576,112 +587,6 @@ async function loadOpenClawContext() {
|
|
|
576
587
|
};
|
|
577
588
|
}
|
|
578
589
|
|
|
579
|
-
// src/lib/npm-install.ts
|
|
580
|
-
import fs4 from "fs/promises";
|
|
581
|
-
import path3 from "path";
|
|
582
|
-
import { createHash } from "crypto";
|
|
583
|
-
import { spawn } from "child_process";
|
|
584
|
-
import fetch2 from "node-fetch";
|
|
585
|
-
function parseNpmPackageSpec(spec) {
|
|
586
|
-
if (spec.startsWith("@")) {
|
|
587
|
-
const versionAt2 = spec.indexOf("@", 1);
|
|
588
|
-
if (versionAt2 === -1) return { packageName: spec, version: "latest" };
|
|
589
|
-
return {
|
|
590
|
-
packageName: spec.slice(0, versionAt2),
|
|
591
|
-
version: spec.slice(versionAt2 + 1) || "latest"
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
const versionAt = spec.indexOf("@");
|
|
595
|
-
if (versionAt === -1) return { packageName: spec, version: "latest" };
|
|
596
|
-
return {
|
|
597
|
-
packageName: spec.slice(0, versionAt),
|
|
598
|
-
version: spec.slice(versionAt + 1) || "latest"
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
async function fetchNpmPackageVersion(packageName, version = "latest") {
|
|
602
|
-
const encodedName = packageName.startsWith("@") ? `@${encodeURIComponent(packageName.slice(1))}` : encodeURIComponent(packageName);
|
|
603
|
-
const url = `https://registry.npmjs.org/${encodedName}/${encodeURIComponent(version)}`;
|
|
604
|
-
const response = await fetch2(url, { headers: { Accept: "application/json" } });
|
|
605
|
-
if (!response.ok) {
|
|
606
|
-
if (response.status === 404) {
|
|
607
|
-
throw new Error(`npm package "${packageName}" not found in the registry.`);
|
|
608
|
-
}
|
|
609
|
-
throw new Error(`npm registry request failed: HTTP ${response.status}`);
|
|
610
|
-
}
|
|
611
|
-
const data = await response.json();
|
|
612
|
-
if (!data?.dist?.tarball) {
|
|
613
|
-
throw new Error(`npm package "${packageName}" has no tarball URL in registry metadata.`);
|
|
614
|
-
}
|
|
615
|
-
return data;
|
|
616
|
-
}
|
|
617
|
-
async function downloadNpmTarball(tarballUrl, destPath, expectedShasum) {
|
|
618
|
-
const response = await fetch2(tarballUrl);
|
|
619
|
-
if (!response.ok) {
|
|
620
|
-
throw new Error(`Failed to download tarball: HTTP ${response.status}`);
|
|
621
|
-
}
|
|
622
|
-
const bytes = Buffer.from(await response.arrayBuffer());
|
|
623
|
-
const actual = createHash("sha1").update(bytes).digest("hex");
|
|
624
|
-
if (actual !== expectedShasum) {
|
|
625
|
-
throw new Error(
|
|
626
|
-
`Tarball integrity check failed: expected ${expectedShasum}, got ${actual}.`
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
await fs4.writeFile(destPath, bytes);
|
|
630
|
-
}
|
|
631
|
-
async function extractNpmTarball(tarballPath, destDir) {
|
|
632
|
-
await new Promise((resolve, reject) => {
|
|
633
|
-
const child = spawn("tar", ["-xzf", tarballPath, "-C", destDir]);
|
|
634
|
-
let stderr = "";
|
|
635
|
-
child.stderr?.on("data", (chunk) => {
|
|
636
|
-
stderr += String(chunk);
|
|
637
|
-
});
|
|
638
|
-
child.on("error", (err) => {
|
|
639
|
-
if (err.code === "ENOENT") {
|
|
640
|
-
reject(new Error('"tar" is required to install npm packages but was not found in PATH.'));
|
|
641
|
-
} else {
|
|
642
|
-
reject(err);
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
child.on("close", (code) => {
|
|
646
|
-
if (code === 0) {
|
|
647
|
-
resolve();
|
|
648
|
-
} else {
|
|
649
|
-
reject(
|
|
650
|
-
new Error(`tar exited with code ${code}${stderr.trim() ? `: ${stderr.trim()}` : ""}`)
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
async function findBotZipInExtractedPackage(extractedDir) {
|
|
657
|
-
const packageRoot = path3.join(extractedDir, "package");
|
|
658
|
-
async function scan(dir) {
|
|
659
|
-
let entries;
|
|
660
|
-
try {
|
|
661
|
-
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
662
|
-
} catch {
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
for (const entry of entries) {
|
|
666
|
-
if (entry.isFile() && entry.name === "bot.zip") {
|
|
667
|
-
return path3.join(dir, entry.name);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
for (const entry of entries) {
|
|
671
|
-
if (entry.isFile() && entry.name.endsWith(".zip")) {
|
|
672
|
-
return path3.join(dir, entry.name);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
for (const entry of entries) {
|
|
676
|
-
if (!entry.isDirectory()) continue;
|
|
677
|
-
const found = await scan(path3.join(dir, entry.name));
|
|
678
|
-
if (found) return found;
|
|
679
|
-
}
|
|
680
|
-
return null;
|
|
681
|
-
}
|
|
682
|
-
return scan(packageRoot);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
590
|
// src/commands/config.ts
|
|
686
591
|
function isValidDownloadToken(token) {
|
|
687
592
|
return isClawMarketDownloadToken(token);
|
|
@@ -692,13 +597,13 @@ function assertValidDownloadToken(token) {
|
|
|
692
597
|
}
|
|
693
598
|
}
|
|
694
599
|
async function resolveSafeDownloadPath(targetPath) {
|
|
695
|
-
const resolved =
|
|
600
|
+
const resolved = path3.resolve(targetPath);
|
|
696
601
|
if (!await fileExists(resolved)) {
|
|
697
602
|
return { outputPath: resolved, renamed: false };
|
|
698
603
|
}
|
|
699
|
-
const parsed =
|
|
604
|
+
const parsed = path3.parse(resolved);
|
|
700
605
|
for (let i = 1; i <= 1e3; i += 1) {
|
|
701
|
-
const candidate =
|
|
606
|
+
const candidate = path3.join(parsed.dir, `${parsed.name}-${i}${parsed.ext}`);
|
|
702
607
|
if (!await fileExists(candidate)) {
|
|
703
608
|
return { outputPath: candidate, renamed: true };
|
|
704
609
|
}
|
|
@@ -709,7 +614,7 @@ function getDefaultServer() {
|
|
|
709
614
|
return process.env.CLAWMARKET_API_URL || "http://localhost:8000";
|
|
710
615
|
}
|
|
711
616
|
function sha256(content) {
|
|
712
|
-
return
|
|
617
|
+
return createHash("sha256").update(content).digest("hex");
|
|
713
618
|
}
|
|
714
619
|
function verifyDownloadedArtifactChecksum(content, checksumHeader) {
|
|
715
620
|
if (!checksumHeader) return;
|
|
@@ -726,7 +631,7 @@ function sanitizeSlugPart(value) {
|
|
|
726
631
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
727
632
|
}
|
|
728
633
|
function sanitizeFileName(value) {
|
|
729
|
-
const base =
|
|
634
|
+
const base = path3.basename(value || "").replace(/[^A-Za-z0-9._-]/g, "-");
|
|
730
635
|
return base || "download.bin";
|
|
731
636
|
}
|
|
732
637
|
function parseContentDispositionFileName(headerValue) {
|
|
@@ -745,6 +650,17 @@ function parseContentDispositionFileName(headerValue) {
|
|
|
745
650
|
if (plain?.[1]) return sanitizeFileName(plain[1].trim());
|
|
746
651
|
return null;
|
|
747
652
|
}
|
|
653
|
+
function normalizeCronJobs(raw) {
|
|
654
|
+
if (Array.isArray(raw)) {
|
|
655
|
+
return raw.filter((item) => !!item && typeof item === "object");
|
|
656
|
+
}
|
|
657
|
+
if (raw && typeof raw === "object" && Array.isArray(raw.jobs)) {
|
|
658
|
+
return raw.jobs.filter(
|
|
659
|
+
(item) => !!item && typeof item === "object"
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
748
664
|
function toStringOrUndefined(value) {
|
|
749
665
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
750
666
|
}
|
|
@@ -762,7 +678,7 @@ async function readJsonError(response) {
|
|
|
762
678
|
}
|
|
763
679
|
}
|
|
764
680
|
async function fetchDownloadArtifact(server, token) {
|
|
765
|
-
const response = await
|
|
681
|
+
const response = await fetch2(`${server}/download/${encodeURIComponent(token)}?format=file`, {
|
|
766
682
|
method: "GET"
|
|
767
683
|
});
|
|
768
684
|
if (!response.ok) {
|
|
@@ -779,7 +695,7 @@ async function fetchDownloadArtifact(server, token) {
|
|
|
779
695
|
}
|
|
780
696
|
async function runCommand(command, args, options = {}) {
|
|
781
697
|
return new Promise((resolve, reject) => {
|
|
782
|
-
const child =
|
|
698
|
+
const child = spawn(command, args, {
|
|
783
699
|
cwd: options.cwd,
|
|
784
700
|
stdio: options.stdio === "inherit" ? "inherit" : "pipe"
|
|
785
701
|
});
|
|
@@ -829,73 +745,251 @@ async function extractZipArtifact(artifactPath, destinationDir) {
|
|
|
829
745
|
throw error;
|
|
830
746
|
}
|
|
831
747
|
}
|
|
832
|
-
async function
|
|
833
|
-
|
|
748
|
+
async function readBotInstallerConfig(configPath) {
|
|
749
|
+
if (!await fileExists(configPath)) {
|
|
750
|
+
throw new Error(`Package is missing required config file: ${configPath}`);
|
|
751
|
+
}
|
|
752
|
+
const raw = await fs4.readFile(configPath, "utf-8");
|
|
753
|
+
try {
|
|
754
|
+
return JSON.parse(raw);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
const reason = error instanceof Error ? error.message : "invalid JSON";
|
|
757
|
+
throw new Error(`Package config is invalid JSON (${configPath}): ${reason}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function resolveInstallPaths(botConfig, agentId, stateDir, sourceBase) {
|
|
761
|
+
function applyTemplate(tpl) {
|
|
762
|
+
return tpl.replace(/\{agent_id\}/g, agentId);
|
|
763
|
+
}
|
|
764
|
+
const srcWorkspace = botConfig.paths?.workspace ?? `workspace-${agentId}`;
|
|
765
|
+
const srcAgentState = botConfig.paths?.agentState ?? `agents/${agentId}`;
|
|
766
|
+
const srcMemoryDb = botConfig.paths?.memoryDb ?? null;
|
|
767
|
+
const srcCronJobs = botConfig.paths?.cronJobs ?? null;
|
|
768
|
+
const tplWorkspace = botConfig.paths?.targetWorkspace ?? "workspace-{agent_id}";
|
|
769
|
+
const tplAgentState = botConfig.paths?.targetAgentState ?? "agents/{agent_id}";
|
|
770
|
+
const tplMemoryDb = botConfig.paths?.targetMemoryDb ?? null;
|
|
771
|
+
const targetAgentRoot = path3.join(stateDir, applyTemplate(tplAgentState));
|
|
772
|
+
return {
|
|
773
|
+
sourceWorkspace: path3.join(sourceBase, srcWorkspace),
|
|
774
|
+
sourceAgentState: path3.join(sourceBase, srcAgentState),
|
|
775
|
+
sourceMemoryDb: srcMemoryDb ? path3.join(sourceBase, srcMemoryDb) : null,
|
|
776
|
+
sourceCronJobs: srcCronJobs ? path3.join(sourceBase, srcCronJobs) : null,
|
|
777
|
+
targetWorkspace: path3.join(stateDir, applyTemplate(tplWorkspace)),
|
|
778
|
+
targetAgentRoot,
|
|
779
|
+
targetAgentDir: path3.join(targetAgentRoot, "agent"),
|
|
780
|
+
targetMemoryDb: tplMemoryDb ? path3.join(stateDir, applyTemplate(tplMemoryDb)) : null
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
async function findSourceRoot(extractedDir) {
|
|
784
|
+
const queue = [path3.resolve(extractedDir)];
|
|
834
785
|
const seen = /* @__PURE__ */ new Set();
|
|
835
786
|
while (queue.length > 0) {
|
|
836
787
|
const current = queue.shift();
|
|
837
788
|
if (!current || seen.has(current)) continue;
|
|
838
789
|
seen.add(current);
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const configPath = path4.join(current, "source", "bot-config.json");
|
|
842
|
-
if (await fileExists(rootInstallerPath) && await fileExists(configPath)) {
|
|
843
|
-
return {
|
|
844
|
-
packageRoot: current,
|
|
845
|
-
installerPath: rootInstallerPath,
|
|
846
|
-
botConfigPath: configPath,
|
|
847
|
-
layout: "root-installer"
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
if (await fileExists(sourceInstallerPath) && await fileExists(configPath)) {
|
|
851
|
-
return {
|
|
852
|
-
packageRoot: current,
|
|
853
|
-
installerPath: sourceInstallerPath,
|
|
854
|
-
botConfigPath: configPath,
|
|
855
|
-
layout: "source-installer"
|
|
856
|
-
};
|
|
790
|
+
if (await fileExists(path3.join(current, "source", "bot-config.json"))) {
|
|
791
|
+
return current;
|
|
857
792
|
}
|
|
858
793
|
let entries;
|
|
859
794
|
try {
|
|
860
|
-
entries = await
|
|
795
|
+
entries = await fs4.readdir(current, { withFileTypes: true });
|
|
861
796
|
} catch {
|
|
862
797
|
continue;
|
|
863
798
|
}
|
|
864
799
|
for (const entry of entries) {
|
|
865
|
-
if (!entry.isDirectory()) continue;
|
|
866
|
-
|
|
867
|
-
queue.push(path4.join(current, entry.name));
|
|
800
|
+
if (!entry.isDirectory() || entry.name === "__MACOSX") continue;
|
|
801
|
+
queue.push(path3.join(current, entry.name));
|
|
868
802
|
}
|
|
869
803
|
}
|
|
870
804
|
return null;
|
|
871
805
|
}
|
|
872
|
-
async function
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
806
|
+
async function copyDirRecursive(src, dest) {
|
|
807
|
+
await fs4.mkdir(dest, { recursive: true });
|
|
808
|
+
const entries = await fs4.readdir(src, { withFileTypes: true });
|
|
809
|
+
for (const entry of entries) {
|
|
810
|
+
const srcPath = path3.join(src, entry.name);
|
|
811
|
+
const destPath = path3.join(dest, entry.name);
|
|
812
|
+
if (entry.isDirectory()) {
|
|
813
|
+
await copyDirRecursive(srcPath, destPath);
|
|
814
|
+
} else {
|
|
815
|
+
await fs4.copyFile(srcPath, destPath);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
async function stripMacOsJunk(dir) {
|
|
820
|
+
const macosDir = path3.join(dir, "__MACOSX");
|
|
821
|
+
await fs4.rm(macosDir, { recursive: true, force: true }).catch(() => void 0);
|
|
822
|
+
async function removeDsStore(d) {
|
|
823
|
+
let entries;
|
|
824
|
+
try {
|
|
825
|
+
entries = await fs4.readdir(d, { withFileTypes: true });
|
|
826
|
+
} catch {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
for (const entry of entries) {
|
|
830
|
+
const full = path3.join(d, entry.name);
|
|
831
|
+
if (entry.isFile() && entry.name === ".DS_Store") {
|
|
832
|
+
await fs4.rm(full, { force: true }).catch(() => void 0);
|
|
833
|
+
} else if (entry.isDirectory()) {
|
|
834
|
+
await removeDsStore(full);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
await removeDsStore(dir);
|
|
839
|
+
}
|
|
840
|
+
async function buildOpenClawConfigPatch(configPath, agentId, agentName, botConfig, installPaths) {
|
|
841
|
+
const raw = await fs4.readFile(configPath, "utf-8");
|
|
842
|
+
const config = JSON52.parse(raw);
|
|
843
|
+
const agentEntry = {
|
|
844
|
+
id: agentId,
|
|
845
|
+
name: agentName,
|
|
846
|
+
workspace: installPaths.targetWorkspace,
|
|
847
|
+
agentDir: installPaths.targetAgentDir
|
|
848
|
+
};
|
|
849
|
+
if (botConfig.agent?.heartbeat) agentEntry.heartbeat = botConfig.agent.heartbeat;
|
|
850
|
+
if (botConfig.agent?.identity) agentEntry.identity = botConfig.agent.identity;
|
|
851
|
+
const agents = config.agents ?? {};
|
|
852
|
+
if (!Array.isArray(agents.list)) agents.list = [];
|
|
853
|
+
agents.list.push(agentEntry);
|
|
854
|
+
config.agents = agents;
|
|
855
|
+
if (botConfig.tools?.web?.search?.enabled === true || botConfig.tools?.web?.fetch?.enabled === true) {
|
|
856
|
+
const tools = config.tools ?? {};
|
|
857
|
+
const web = tools.web ?? {};
|
|
858
|
+
if (botConfig.tools?.web?.search?.enabled === true) {
|
|
859
|
+
const s = web.search ?? {};
|
|
860
|
+
s.enabled = true;
|
|
861
|
+
web.search = s;
|
|
862
|
+
}
|
|
863
|
+
if (botConfig.tools?.web?.fetch?.enabled === true) {
|
|
864
|
+
const f = web.fetch ?? {};
|
|
865
|
+
f.enabled = true;
|
|
866
|
+
web.fetch = f;
|
|
867
|
+
}
|
|
868
|
+
tools.web = web;
|
|
869
|
+
config.tools = tools;
|
|
879
870
|
}
|
|
880
|
-
const promotedInstallerPath = path4.join(pkg.packageRoot, "installer.sh");
|
|
881
|
-
await fs5.copyFile(pkg.installerPath, promotedInstallerPath);
|
|
882
|
-
await fs5.chmod(promotedInstallerPath, 493).catch(() => void 0);
|
|
883
871
|
return {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
botConfigPath: pkg.botConfigPath
|
|
872
|
+
configContent: JSON.stringify(config, null, 2) + "\n",
|
|
873
|
+
configBackupContent: raw
|
|
887
874
|
};
|
|
888
875
|
}
|
|
889
|
-
async function
|
|
890
|
-
if (!await fileExists(
|
|
891
|
-
|
|
876
|
+
async function prepareCronJobsFromBundle(sourceCronPath, cronStorePath, agentId) {
|
|
877
|
+
if (!sourceCronPath || !await fileExists(sourceCronPath)) return null;
|
|
878
|
+
const raw = await fs4.readFile(sourceCronPath, "utf-8");
|
|
879
|
+
const bundleJobs = normalizeCronJobs(JSON.parse(raw));
|
|
880
|
+
if (bundleJobs.length === 0) return null;
|
|
881
|
+
const shouldInstall = await resolveCronChoice(true);
|
|
882
|
+
if (!shouldInstall) return null;
|
|
883
|
+
const newJobs = bundleJobs.map((j) => ({
|
|
884
|
+
...j,
|
|
885
|
+
agentId,
|
|
886
|
+
state: {},
|
|
887
|
+
delivery: { mode: "announce", channel: "last", bestEffort: true }
|
|
888
|
+
}));
|
|
889
|
+
let existed = false;
|
|
890
|
+
let backupContent = null;
|
|
891
|
+
let existingJobs = [];
|
|
892
|
+
if (await fileExists(cronStorePath)) {
|
|
893
|
+
existed = true;
|
|
894
|
+
backupContent = await fs4.readFile(cronStorePath, "utf-8");
|
|
895
|
+
existingJobs = normalizeCronJobs(JSON52.parse(backupContent));
|
|
892
896
|
}
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
897
|
+
const existingKeys = new Set(
|
|
898
|
+
existingJobs.map((j) => `${String(j.agentId)}::${String(j.name)}`)
|
|
899
|
+
);
|
|
900
|
+
const deduped = newJobs.filter(
|
|
901
|
+
(j) => !existingKeys.has(`${String(j.agentId)}::${String(j.name)}`)
|
|
902
|
+
);
|
|
903
|
+
if (deduped.length === 0) {
|
|
904
|
+
console.log("All cron jobs already present, skipping.");
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
const merged = [...existingJobs, ...deduped];
|
|
908
|
+
return {
|
|
909
|
+
content: JSON.stringify({ version: 1, jobs: merged }, null, 2) + "\n",
|
|
910
|
+
existed,
|
|
911
|
+
backupContent,
|
|
912
|
+
jobCount: deduped.length
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
|
|
916
|
+
if (!botConfig.apiKeys?.enabled) return;
|
|
917
|
+
console.log("");
|
|
918
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
919
|
+
console.log(" API Key Setup");
|
|
920
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
921
|
+
console.log(" You can skip any key now and add it later.");
|
|
922
|
+
const brave = botConfig.apiKeys.braveSearch;
|
|
923
|
+
if (brave?.enabled) {
|
|
924
|
+
console.log("");
|
|
925
|
+
const key = await askOptional(brave.prompt ?? "Brave Search API key", brave.url);
|
|
926
|
+
if (key) {
|
|
927
|
+
const raw = await fs4.readFile(configPath, "utf-8");
|
|
928
|
+
const config = JSON52.parse(raw);
|
|
929
|
+
const tools = config.tools ?? {};
|
|
930
|
+
const web = tools.web ?? {};
|
|
931
|
+
const search = web.search ?? {};
|
|
932
|
+
search.apiKey = key;
|
|
933
|
+
web.search = search;
|
|
934
|
+
tools.web = web;
|
|
935
|
+
config.tools = tools;
|
|
936
|
+
await fs4.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
937
|
+
console.log(" Brave Search key saved to openclaw.json");
|
|
938
|
+
} else {
|
|
939
|
+
console.log(" Skipped Brave Search key.");
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
const envKeys = botConfig.apiKeys.env ?? [];
|
|
943
|
+
if (envKeys.length > 0) {
|
|
944
|
+
await fs4.mkdir(path3.dirname(envPath), { recursive: true });
|
|
945
|
+
let envContent = "";
|
|
946
|
+
try {
|
|
947
|
+
envContent = await fs4.readFile(envPath, "utf-8");
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
let keysWritten = 0;
|
|
951
|
+
for (const keyDef of envKeys) {
|
|
952
|
+
console.log("");
|
|
953
|
+
const val = await askOptional(keyDef.prompt ?? keyDef.key, keyDef.url);
|
|
954
|
+
if (val) {
|
|
955
|
+
const regex = new RegExp(`^${keyDef.key}=.*$`, "m");
|
|
956
|
+
if (regex.test(envContent)) {
|
|
957
|
+
envContent = envContent.replace(regex, `${keyDef.key}=${val}`);
|
|
958
|
+
} else {
|
|
959
|
+
envContent = envContent.trimEnd() + `
|
|
960
|
+
${keyDef.key}=${val}`;
|
|
961
|
+
}
|
|
962
|
+
keysWritten++;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (keysWritten > 0) {
|
|
966
|
+
await fs4.writeFile(envPath, envContent.trim() + "\n", "utf-8");
|
|
967
|
+
console.log(`
|
|
968
|
+
${keysWritten} key(s) saved to .env`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
async function validateInstalledWorkspace(workspacePath, botConfig) {
|
|
973
|
+
const requiredFiles = botConfig.validation?.requiredFiles ?? [];
|
|
974
|
+
const requiredDirs = botConfig.validation?.requiredDirs ?? [];
|
|
975
|
+
if (requiredFiles.length === 0 && requiredDirs.length === 0) return;
|
|
976
|
+
const warnings = [];
|
|
977
|
+
for (const file of requiredFiles) {
|
|
978
|
+
if (!await fileExists(path3.join(workspacePath, file))) {
|
|
979
|
+
warnings.push(`Missing expected file: ${file}`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
for (const dir of requiredDirs) {
|
|
983
|
+
try {
|
|
984
|
+
const stat = await fs4.stat(path3.join(workspacePath, dir));
|
|
985
|
+
if (!stat.isDirectory()) warnings.push(`Expected directory but found file: ${dir}`);
|
|
986
|
+
} catch {
|
|
987
|
+
warnings.push(`Missing expected directory: ${dir}`);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (warnings.length > 0) {
|
|
991
|
+
console.log("\nValidation warnings:");
|
|
992
|
+
for (const w of warnings) console.warn(` \u26A0 ${w}`);
|
|
899
993
|
}
|
|
900
994
|
}
|
|
901
995
|
async function resolveAgentId(suggestedAgentId) {
|
|
@@ -909,13 +1003,64 @@ async function resolveAgentId(suggestedAgentId) {
|
|
|
909
1003
|
}
|
|
910
1004
|
return prompted;
|
|
911
1005
|
}
|
|
1006
|
+
async function resolveCronChoice(hasPackagedCronJobs) {
|
|
1007
|
+
if (!hasPackagedCronJobs) return false;
|
|
1008
|
+
const canPrompt = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1009
|
+
if (!canPrompt) {
|
|
1010
|
+
throw new Error("Install requires an interactive terminal to confirm cron job installation.");
|
|
1011
|
+
}
|
|
1012
|
+
return confirm("Install packaged cron jobs for this new agent?", true);
|
|
1013
|
+
}
|
|
1014
|
+
async function commitInstallTransaction(input2) {
|
|
1015
|
+
let movedWorkspace = false;
|
|
1016
|
+
let movedAgentDir = false;
|
|
1017
|
+
let copiedMemoryDb = false;
|
|
1018
|
+
try {
|
|
1019
|
+
await fs4.mkdir(path3.dirname(input2.workspacePath), { recursive: true });
|
|
1020
|
+
await fs4.rename(input2.stagedWorkspacePath, input2.workspacePath);
|
|
1021
|
+
movedWorkspace = true;
|
|
1022
|
+
await fs4.mkdir(path3.dirname(input2.agentDirPath), { recursive: true });
|
|
1023
|
+
await fs4.rename(input2.stagedAgentDirPath, input2.agentDirPath);
|
|
1024
|
+
movedAgentDir = true;
|
|
1025
|
+
if (input2.memoryDbStagedPath && input2.memoryDbTargetPath && await fileExists(input2.memoryDbStagedPath)) {
|
|
1026
|
+
await fs4.mkdir(path3.dirname(input2.memoryDbTargetPath), { recursive: true });
|
|
1027
|
+
await fs4.copyFile(input2.memoryDbStagedPath, input2.memoryDbTargetPath);
|
|
1028
|
+
copiedMemoryDb = true;
|
|
1029
|
+
}
|
|
1030
|
+
if (input2.cronContent !== null) {
|
|
1031
|
+
await fs4.mkdir(path3.dirname(input2.cronStorePath), { recursive: true });
|
|
1032
|
+
await fs4.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
|
|
1033
|
+
}
|
|
1034
|
+
await fs4.writeFile(input2.configPath, input2.configContent, "utf-8");
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
if (input2.cronContent !== null) {
|
|
1037
|
+
if (input2.cronExisted && input2.cronBackupContent !== null) {
|
|
1038
|
+
await fs4.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
|
|
1039
|
+
} else {
|
|
1040
|
+
await fs4.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (copiedMemoryDb && input2.memoryDbTargetPath) {
|
|
1044
|
+
await fs4.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
|
|
1045
|
+
}
|
|
1046
|
+
await fs4.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
|
|
1047
|
+
if (movedWorkspace) {
|
|
1048
|
+
await fs4.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
|
|
1049
|
+
}
|
|
1050
|
+
if (movedAgentDir) {
|
|
1051
|
+
await fs4.rm(input2.agentRootPath, { recursive: true, force: true }).catch(() => void 0);
|
|
1052
|
+
}
|
|
1053
|
+
const message = error instanceof Error ? error.message : "unknown failure";
|
|
1054
|
+
throw new Error(`Install failed and was rolled back: ${message}`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
912
1057
|
function registerConfigCommands(program2) {
|
|
913
1058
|
const configCommand = program2.command("config").description("Download and install published configs");
|
|
914
1059
|
configCommand.command("download").description("Download a published config artifact from a one-time token").argument("<token>", "One-time download token").action(async (token) => {
|
|
915
1060
|
try {
|
|
916
1061
|
assertValidDownloadToken(token);
|
|
917
1062
|
const server = getDefaultServer();
|
|
918
|
-
const response = await
|
|
1063
|
+
const response = await fetch2(`${server}/download/${encodeURIComponent(token)}?format=file`, {
|
|
919
1064
|
method: "GET"
|
|
920
1065
|
});
|
|
921
1066
|
if (!response.ok) {
|
|
@@ -923,12 +1068,12 @@ function registerConfigCommands(program2) {
|
|
|
923
1068
|
}
|
|
924
1069
|
const fileFromHeader = parseContentDispositionFileName(response.headers.get("content-disposition"));
|
|
925
1070
|
const fallbackFile = `clawmarket-${token}.bin`;
|
|
926
|
-
const preferredPath =
|
|
1071
|
+
const preferredPath = path3.resolve(fileFromHeader || fallbackFile);
|
|
927
1072
|
const { outputPath, renamed } = await resolveSafeDownloadPath(preferredPath);
|
|
928
1073
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
929
1074
|
verifyDownloadedArtifactChecksum(bytes, response.headers.get(CLAWMARKET_ARTIFACT_SHA256_HEADER));
|
|
930
|
-
await
|
|
931
|
-
await
|
|
1075
|
+
await fs4.mkdir(path3.dirname(outputPath), { recursive: true });
|
|
1076
|
+
await fs4.writeFile(outputPath, bytes);
|
|
932
1077
|
if (renamed) {
|
|
933
1078
|
console.log(`Existing file detected, saved as: ${outputPath}`);
|
|
934
1079
|
}
|
|
@@ -945,21 +1090,24 @@ function registerConfigCommands(program2) {
|
|
|
945
1090
|
const server = getDefaultServer();
|
|
946
1091
|
const context = await loadOpenClawContext();
|
|
947
1092
|
const artifact = await fetchDownloadArtifact(server, token);
|
|
948
|
-
const tempRoot = await
|
|
1093
|
+
const tempRoot = await fs4.mkdtemp(
|
|
1094
|
+
path3.join(os3.tmpdir(), `.clawmarket-install-${randomUUID().slice(0, 8)}-`)
|
|
1095
|
+
);
|
|
949
1096
|
try {
|
|
950
|
-
const artifactPath =
|
|
951
|
-
const extractedPath =
|
|
952
|
-
await
|
|
953
|
-
await
|
|
1097
|
+
const artifactPath = path3.join(tempRoot, sanitizeFileName(artifact.fileName));
|
|
1098
|
+
const extractedPath = path3.join(tempRoot, "extracted");
|
|
1099
|
+
await fs4.mkdir(extractedPath, { recursive: true });
|
|
1100
|
+
await fs4.writeFile(artifactPath, artifact.bytes);
|
|
954
1101
|
await extractZipArtifact(artifactPath, extractedPath);
|
|
955
|
-
|
|
956
|
-
|
|
1102
|
+
await stripMacOsJunk(extractedPath);
|
|
1103
|
+
const sourceRoot = await findSourceRoot(extractedPath);
|
|
1104
|
+
if (!sourceRoot) {
|
|
957
1105
|
throw new Error(
|
|
958
|
-
"Downloaded artifact is not an installable bot package (missing
|
|
1106
|
+
"Downloaded artifact is not an installable bot package (missing source/bot-config.json)."
|
|
959
1107
|
);
|
|
960
1108
|
}
|
|
961
|
-
const
|
|
962
|
-
const botConfig = await readBotInstallerConfig(
|
|
1109
|
+
const sourceBase = path3.join(sourceRoot, "source");
|
|
1110
|
+
const botConfig = await readBotInstallerConfig(path3.join(sourceBase, "bot-config.json"));
|
|
963
1111
|
if (typeof botConfig.installerSpecVersion === "number" && botConfig.installerSpecVersion !== 1) {
|
|
964
1112
|
console.warn(
|
|
965
1113
|
`Warning: installerSpecVersion=${botConfig.installerSpecVersion} (this CLI currently expects version 1).`
|
|
@@ -969,110 +1117,77 @@ function registerConfigCommands(program2) {
|
|
|
969
1117
|
const suggestedAgentId = validateAgentId(configuredAgentId) ? configuredAgentId : sanitizeSlugPart(configuredAgentId) || "agent";
|
|
970
1118
|
const agentId = await resolveAgentId(suggestedAgentId);
|
|
971
1119
|
const agentName = toStringOrUndefined(botConfig.agent?.name) || agentId;
|
|
972
|
-
const
|
|
973
|
-
if (
|
|
1120
|
+
const existingIds = new Set(context.agents.map((a) => a.id.toLowerCase()));
|
|
1121
|
+
if (existingIds.has(agentId.toLowerCase())) {
|
|
974
1122
|
throw new Error(`Agent id "${agentId}" already exists. Choose another id.`);
|
|
975
1123
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1124
|
+
const installPaths = resolveInstallPaths(botConfig, agentId, context.paths.stateDir, sourceBase);
|
|
1125
|
+
const stagedWorkspacePath = path3.join(tempRoot, "staged-workspace");
|
|
1126
|
+
const stagedAgentDirPath = path3.join(tempRoot, "staged-agent");
|
|
1127
|
+
await copyDirRecursive(installPaths.sourceWorkspace, stagedWorkspacePath);
|
|
1128
|
+
await copyDirRecursive(installPaths.sourceAgentState, stagedAgentDirPath);
|
|
1129
|
+
const { configContent, configBackupContent } = await buildOpenClawConfigPatch(
|
|
1130
|
+
context.paths.configPath,
|
|
1131
|
+
agentId,
|
|
1132
|
+
agentName,
|
|
1133
|
+
botConfig,
|
|
1134
|
+
installPaths
|
|
1135
|
+
);
|
|
1136
|
+
const cronResult = await prepareCronJobsFromBundle(
|
|
1137
|
+
installPaths.sourceCronJobs,
|
|
1138
|
+
context.paths.cronStorePath,
|
|
1139
|
+
agentId
|
|
981
1140
|
);
|
|
1141
|
+
console.log("\nInstalling...");
|
|
1142
|
+
await commitInstallTransaction({
|
|
1143
|
+
stagedWorkspacePath,
|
|
1144
|
+
stagedAgentDirPath,
|
|
1145
|
+
workspacePath: installPaths.targetWorkspace,
|
|
1146
|
+
agentDirPath: installPaths.targetAgentDir,
|
|
1147
|
+
agentRootPath: installPaths.targetAgentRoot,
|
|
1148
|
+
configPath: context.paths.configPath,
|
|
1149
|
+
configContent,
|
|
1150
|
+
configBackupContent,
|
|
1151
|
+
cronStorePath: context.paths.cronStorePath,
|
|
1152
|
+
cronContent: cronResult?.content ?? null,
|
|
1153
|
+
cronExisted: cronResult?.existed ?? false,
|
|
1154
|
+
cronBackupContent: cronResult?.backupContent ?? null,
|
|
1155
|
+
memoryDbStagedPath: installPaths.sourceMemoryDb,
|
|
1156
|
+
memoryDbTargetPath: installPaths.targetMemoryDb
|
|
1157
|
+
});
|
|
1158
|
+
await setupApiKeysFromConfig(botConfig, context.paths.configPath, context.paths.envPath);
|
|
1159
|
+
const installerShPath = path3.join(sourceBase, "installer.sh");
|
|
1160
|
+
if (await fileExists(installerShPath)) {
|
|
1161
|
+
console.log("\nRunning post-install hook (installer.sh)...");
|
|
1162
|
+
await runCommand("bash", [installerShPath, context.paths.stateDir], {
|
|
1163
|
+
cwd: sourceBase,
|
|
1164
|
+
stdio: "inherit"
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
await validateInstalledWorkspace(installPaths.targetWorkspace, botConfig);
|
|
982
1168
|
const refreshed = await loadOpenClawContext();
|
|
983
|
-
const installed = refreshed.agents.find((
|
|
1169
|
+
const installed = refreshed.agents.find((a) => a.id.toLowerCase() === agentId.toLowerCase());
|
|
984
1170
|
if (!installed) {
|
|
985
|
-
throw new Error("
|
|
1171
|
+
throw new Error("Install finished, but the new agent was not detected in openclaw.json.");
|
|
986
1172
|
}
|
|
987
|
-
console.log("
|
|
988
|
-
console.log(
|
|
989
|
-
console.log(
|
|
1173
|
+
console.log("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1174
|
+
console.log(" Installed successfully into OpenClaw");
|
|
1175
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1176
|
+
console.log(` Agent ID: ${installed.id}`);
|
|
1177
|
+
console.log(` Name: ${installed.name}`);
|
|
990
1178
|
console.log(` Workspace: ${installed.workspace}`);
|
|
991
1179
|
console.log(` Agent Dir: ${installed.agentDir}`);
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
console.log(" 2) openclaw gateway restart");
|
|
995
|
-
} finally {
|
|
996
|
-
await fs5.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
997
|
-
}
|
|
998
|
-
} catch (error) {
|
|
999
|
-
const message = error instanceof Error ? error.message : "Install failed";
|
|
1000
|
-
console.error(message);
|
|
1001
|
-
process.exit(1);
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
configCommand.command("install-npm").description("Install a bot config from an npm package (e.g. @clawmarket/my-bot)").argument("<package>", "npm package name, optionally with version (e.g. @clawmarket/my-bot or @clawmarket/my-bot@1.2.0)").action(async (packageSpec) => {
|
|
1005
|
-
try {
|
|
1006
|
-
const { packageName, version } = parseNpmPackageSpec(packageSpec);
|
|
1007
|
-
console.log(`Looking up ${packageSpec} in the npm registry...`);
|
|
1008
|
-
const context = await loadOpenClawContext();
|
|
1009
|
-
const pkgMeta = await fetchNpmPackageVersion(packageName, version);
|
|
1010
|
-
console.log(`Found ${pkgMeta.name}@${pkgMeta.version}`);
|
|
1011
|
-
const tempRoot = await fs5.mkdtemp(
|
|
1012
|
-
path4.join(os3.tmpdir(), `.clawmarket-npm-${randomUUID().slice(0, 8)}-`)
|
|
1013
|
-
);
|
|
1014
|
-
try {
|
|
1015
|
-
const tarballPath = path4.join(tempRoot, "package.tgz");
|
|
1016
|
-
console.log("Downloading package...");
|
|
1017
|
-
await downloadNpmTarball(pkgMeta.dist.tarball, tarballPath, pkgMeta.dist.shasum);
|
|
1018
|
-
const npmExtractedPath = path4.join(tempRoot, "npm-extracted");
|
|
1019
|
-
await fs5.mkdir(npmExtractedPath, { recursive: true });
|
|
1020
|
-
await extractNpmTarball(tarballPath, npmExtractedPath);
|
|
1021
|
-
const botZipPath = await findBotZipInExtractedPackage(npmExtractedPath);
|
|
1022
|
-
if (!botZipPath) {
|
|
1023
|
-
throw new Error(
|
|
1024
|
-
`npm package "${pkgMeta.name}" does not contain a bot.zip. Expected a bot.zip at the package root inside the tarball.`
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1027
|
-
const botZipFileName = sanitizeFileName(path4.basename(botZipPath));
|
|
1028
|
-
const stagedZipPath = path4.join(tempRoot, botZipFileName);
|
|
1029
|
-
await fs5.copyFile(botZipPath, stagedZipPath);
|
|
1030
|
-
const botExtractedPath = path4.join(tempRoot, "bot-extracted");
|
|
1031
|
-
await fs5.mkdir(botExtractedPath, { recursive: true });
|
|
1032
|
-
await extractZipArtifact(stagedZipPath, botExtractedPath);
|
|
1033
|
-
const packageInfo = await findInstallerPackage(botExtractedPath);
|
|
1034
|
-
if (!packageInfo) {
|
|
1035
|
-
throw new Error(
|
|
1036
|
-
"Bot zip is not an installable package (missing installer.sh + source/bot-config.json)."
|
|
1037
|
-
);
|
|
1180
|
+
if (cronResult) {
|
|
1181
|
+
console.log(` Cron jobs: ${cronResult.jobCount} installed`);
|
|
1038
1182
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
if (typeof botConfig.installerSpecVersion === "number" && botConfig.installerSpecVersion !== 1) {
|
|
1042
|
-
console.warn(
|
|
1043
|
-
`Warning: installerSpecVersion=${botConfig.installerSpecVersion} (this CLI currently expects version 1).`
|
|
1044
|
-
);
|
|
1183
|
+
if (installPaths.targetMemoryDb && await fileExists(installPaths.targetMemoryDb)) {
|
|
1184
|
+
console.log(` Memory DB: ${installPaths.targetMemoryDb}`);
|
|
1045
1185
|
}
|
|
1046
|
-
const configuredAgentId = toStringOrUndefined(botConfig.agent?.id) || "agent";
|
|
1047
|
-
const suggestedAgentId = validateAgentId(configuredAgentId) ? configuredAgentId : sanitizeSlugPart(configuredAgentId) || "agent";
|
|
1048
|
-
const agentId = await resolveAgentId(suggestedAgentId);
|
|
1049
|
-
const agentName = toStringOrUndefined(botConfig.agent?.name) || agentId;
|
|
1050
|
-
const existing = new Set(context.agents.map((agent) => agent.id.toLowerCase()));
|
|
1051
|
-
if (existing.has(agentId.toLowerCase())) {
|
|
1052
|
-
throw new Error(`Agent id "${agentId}" already exists. Choose another id.`);
|
|
1053
|
-
}
|
|
1054
|
-
console.log("Running packaged installer...");
|
|
1055
|
-
await runCommand(
|
|
1056
|
-
"bash",
|
|
1057
|
-
[installLayout.installerScriptName, "--agent-id", agentId, "--agent-name", agentName, context.paths.stateDir],
|
|
1058
|
-
{ cwd: installLayout.packageRoot, stdio: "inherit" }
|
|
1059
|
-
);
|
|
1060
|
-
const refreshed = await loadOpenClawContext();
|
|
1061
|
-
const installed = refreshed.agents.find((agent) => agent.id.toLowerCase() === agentId.toLowerCase());
|
|
1062
|
-
if (!installed) {
|
|
1063
|
-
throw new Error("Installer finished, but the new agent was not detected in openclaw.json.");
|
|
1064
|
-
}
|
|
1065
|
-
console.log(`
|
|
1066
|
-
Installed ${pkgMeta.name}@${pkgMeta.version} into OpenClaw as a new agent`);
|
|
1067
|
-
console.log(` Agent ID: ${installed.id}`);
|
|
1068
|
-
console.log(` Name: ${installed.name}`);
|
|
1069
|
-
console.log(` Workspace: ${installed.workspace}`);
|
|
1070
|
-
console.log(` Agent Dir: ${installed.agentDir}`);
|
|
1071
1186
|
console.log("\nNext steps:");
|
|
1072
1187
|
console.log(" 1) openclaw agents list");
|
|
1073
1188
|
console.log(" 2) openclaw gateway restart");
|
|
1074
1189
|
} finally {
|
|
1075
|
-
await
|
|
1190
|
+
await fs4.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
1076
1191
|
}
|
|
1077
1192
|
} catch (error) {
|
|
1078
1193
|
const message = error instanceof Error ? error.message : "Install failed";
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawmarketbot",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "CLI tool for ClawMarket - discover, download, and install OpenClaw configs",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"clawmarketbot": "
|
|
8
|
+
"clawmarketbot": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"main": "./dist/index.js",
|
|
11
11
|
"exports": {
|