claw-control-center 0.1.7 → 0.1.8
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.cjs +197 -30
- package/dist/install-qclaw.cjs +173 -28
- package/dist/install-qclaw.d.cts +11 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5449,10 +5449,10 @@ function resolvePluginConfig(config) {
|
|
|
5449
5449
|
function resolvePluginConfigWithHostDefaults(configPath, config) {
|
|
5450
5450
|
const resolved = resolvePluginConfig(config);
|
|
5451
5451
|
const hostConfig = readHostGatewayConfig(resolveHostConfigPath(configPath));
|
|
5452
|
-
if (!resolved.gateway.baseUrl
|
|
5452
|
+
if (hostConfig.baseUrl && (!resolved.gateway.baseUrl || shouldPreferHostGateway(resolved.gateway.baseUrl, hostConfig.baseUrl))) {
|
|
5453
5453
|
resolved.gateway.baseUrl = hostConfig.baseUrl;
|
|
5454
5454
|
}
|
|
5455
|
-
if (!resolved.gateway.secret
|
|
5455
|
+
if (hostConfig.secret && (!resolved.gateway.secret || resolved.gateway.baseUrl === hostConfig.baseUrl)) {
|
|
5456
5456
|
resolved.gateway.secret = hostConfig.secret;
|
|
5457
5457
|
}
|
|
5458
5458
|
if (!resolved.hub53ai.botId && hostConfig.hub53ai?.botId) {
|
|
@@ -5466,6 +5466,28 @@ function resolvePluginConfigWithHostDefaults(configPath, config) {
|
|
|
5466
5466
|
}
|
|
5467
5467
|
return resolved;
|
|
5468
5468
|
}
|
|
5469
|
+
function shouldPreferHostGateway(configuredBaseUrl, hostBaseUrl) {
|
|
5470
|
+
if (configuredBaseUrl === hostBaseUrl) {
|
|
5471
|
+
return false;
|
|
5472
|
+
}
|
|
5473
|
+
const configured = parseGatewayUrl(configuredBaseUrl);
|
|
5474
|
+
const host = parseGatewayUrl(hostBaseUrl);
|
|
5475
|
+
if (!configured || !host) {
|
|
5476
|
+
return false;
|
|
5477
|
+
}
|
|
5478
|
+
return configured.isLoopback && host.isLoopback;
|
|
5479
|
+
}
|
|
5480
|
+
function parseGatewayUrl(value) {
|
|
5481
|
+
try {
|
|
5482
|
+
const url = new URL(value);
|
|
5483
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
5484
|
+
return {
|
|
5485
|
+
isLoopback: hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"
|
|
5486
|
+
};
|
|
5487
|
+
} catch {
|
|
5488
|
+
return void 0;
|
|
5489
|
+
}
|
|
5490
|
+
}
|
|
5469
5491
|
function sanitizePluginConfig(config) {
|
|
5470
5492
|
if (!config) {
|
|
5471
5493
|
return {};
|
|
@@ -8859,12 +8881,29 @@ function toRecord3(value) {
|
|
|
8859
8881
|
|
|
8860
8882
|
// src/install-qclaw.ts
|
|
8861
8883
|
var import_node_crypto4 = require("crypto");
|
|
8862
|
-
var import_promises4 = require("
|
|
8884
|
+
var import_promises4 = require("readline/promises");
|
|
8885
|
+
var import_promises5 = require("fs/promises");
|
|
8863
8886
|
var import_node_fs4 = require("fs");
|
|
8887
|
+
var import_node_os2 = require("os");
|
|
8864
8888
|
var import_node_path6 = require("path");
|
|
8865
8889
|
var PLUGIN_ID = "claw-control-center";
|
|
8866
8890
|
var LEGACY_PLUGIN_ID = "53ai-openclaw";
|
|
8867
8891
|
var COPY_ITEMS = ["dist", "openclaw.plugin.json", "package.json", "bin", "web-dist"];
|
|
8892
|
+
var SUPPORTED_ARGS = /* @__PURE__ */ new Set([
|
|
8893
|
+
"gateway",
|
|
8894
|
+
"bot-id",
|
|
8895
|
+
"secret",
|
|
8896
|
+
"prefer-responses-api",
|
|
8897
|
+
"gateway-model",
|
|
8898
|
+
"hub-ws-url",
|
|
8899
|
+
"hub-bot-id",
|
|
8900
|
+
"hub-secret",
|
|
8901
|
+
"hub-enabled",
|
|
8902
|
+
"extensions-dir",
|
|
8903
|
+
"config-path",
|
|
8904
|
+
"console-host",
|
|
8905
|
+
"console-port"
|
|
8906
|
+
]);
|
|
8868
8907
|
async function installIntoQClaw(input) {
|
|
8869
8908
|
return installIntoHost(input, "QClaw");
|
|
8870
8909
|
}
|
|
@@ -8872,19 +8911,21 @@ async function installIntoOpenClaw(input) {
|
|
|
8872
8911
|
return installIntoHost(input, "OpenClaw");
|
|
8873
8912
|
}
|
|
8874
8913
|
async function installIntoHost(input, hostLabel) {
|
|
8875
|
-
await (0,
|
|
8914
|
+
await (0, import_promises5.mkdir)(input.extensionsDir, { recursive: true });
|
|
8876
8915
|
const destination = (0, import_node_path6.join)(input.extensionsDir, PLUGIN_ID);
|
|
8877
|
-
await (0,
|
|
8916
|
+
await (0, import_promises5.mkdir)(destination, { recursive: true });
|
|
8878
8917
|
await copyPublishablePackage(input.packageRoot, destination);
|
|
8879
|
-
await (0,
|
|
8918
|
+
await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(input.configPath), { recursive: true });
|
|
8880
8919
|
const config = await readOpenClawConfig(input.configPath);
|
|
8881
8920
|
const inferredGateway = inferGatewaySettings(config);
|
|
8882
8921
|
const inferredHub53AI = inferHub53AISettings(config);
|
|
8883
|
-
const
|
|
8922
|
+
const explicitGatewayBaseUrl = input.gateway?.trim();
|
|
8923
|
+
const explicitGatewaySecret = input.secret?.trim();
|
|
8924
|
+
const gatewayBaseUrl = explicitGatewayBaseUrl || inferredGateway.baseUrl;
|
|
8884
8925
|
if (!gatewayBaseUrl) {
|
|
8885
8926
|
throw new Error(`missing gateway URL and no local ${hostLabel} gateway could be inferred`);
|
|
8886
8927
|
}
|
|
8887
|
-
const secret =
|
|
8928
|
+
const secret = explicitGatewaySecret || inferredGateway.secret;
|
|
8888
8929
|
if (!secret) {
|
|
8889
8930
|
throw new Error(`missing gateway secret and no local ${hostLabel} gateway token could be inferred`);
|
|
8890
8931
|
}
|
|
@@ -8919,8 +8960,16 @@ async function installIntoHost(input, hostLabel) {
|
|
|
8919
8960
|
const previousEntry = ensureObject(entries, PLUGIN_ID);
|
|
8920
8961
|
const previousConfig = ensureObject(previousEntry, "config");
|
|
8921
8962
|
const previousGateway = ensureObject(previousConfig, "gateway");
|
|
8922
|
-
|
|
8923
|
-
|
|
8963
|
+
if (explicitGatewayBaseUrl) {
|
|
8964
|
+
previousGateway.baseUrl = explicitGatewayBaseUrl;
|
|
8965
|
+
} else {
|
|
8966
|
+
delete previousGateway.baseUrl;
|
|
8967
|
+
}
|
|
8968
|
+
if (explicitGatewaySecret) {
|
|
8969
|
+
previousGateway.secret = explicitGatewaySecret;
|
|
8970
|
+
} else {
|
|
8971
|
+
delete previousGateway.secret;
|
|
8972
|
+
}
|
|
8924
8973
|
previousGateway.preferResponsesApi = preferResponsesApi;
|
|
8925
8974
|
if (botId) {
|
|
8926
8975
|
previousGateway.botId = botId;
|
|
@@ -8961,7 +9010,7 @@ async function installIntoHost(input, hostLabel) {
|
|
|
8961
9010
|
}
|
|
8962
9011
|
}
|
|
8963
9012
|
previousEntry.enabled = true;
|
|
8964
|
-
await (0,
|
|
9013
|
+
await (0, import_promises5.writeFile)(input.configPath, `${JSON.stringify(config, null, 2)}
|
|
8965
9014
|
`);
|
|
8966
9015
|
return {
|
|
8967
9016
|
configPath: input.configPath,
|
|
@@ -8978,7 +9027,11 @@ async function runInstallCommand(input) {
|
|
|
8978
9027
|
throw new Error("expected subcommand: install");
|
|
8979
9028
|
}
|
|
8980
9029
|
const args = parseArgs(argv.slice(1));
|
|
8981
|
-
const destination = resolveInstallDestination(args
|
|
9030
|
+
const destination = await resolveInstallDestination(args, {
|
|
9031
|
+
hostDefinitions: input.hostDefinitions,
|
|
9032
|
+
selectHost: input.selectHost,
|
|
9033
|
+
ttyPath: input.ttyPath
|
|
9034
|
+
});
|
|
8982
9035
|
const result = await installIntoHost(
|
|
8983
9036
|
{
|
|
8984
9037
|
packageRoot: input.packageRoot,
|
|
@@ -8996,34 +9049,141 @@ async function runInstallCommand(input) {
|
|
|
8996
9049
|
consoleHost: args["console-host"],
|
|
8997
9050
|
consolePort: args["console-port"] ? Number(args["console-port"]) : void 0
|
|
8998
9051
|
},
|
|
8999
|
-
|
|
9052
|
+
destination.label
|
|
9000
9053
|
);
|
|
9001
9054
|
process.stdout.write(
|
|
9002
9055
|
[
|
|
9003
|
-
`Installed ${PLUGIN_ID} into
|
|
9056
|
+
`Installed ${PLUGIN_ID} into ${destination.label}.`,
|
|
9004
9057
|
`Extensions: ${result.extensionsDir}`,
|
|
9005
9058
|
`Config: ${result.configPath}`,
|
|
9006
9059
|
`Gateway: ${result.gatewayBaseUrl}`,
|
|
9007
9060
|
`53AIHub: ${result.hub53aiConfigured ? "configured" : "not configured"}`,
|
|
9008
9061
|
`Plugin build: ${result.pluginBuild}`,
|
|
9009
|
-
|
|
9062
|
+
`Restart ${destination.label} to load the plugin.`
|
|
9010
9063
|
].join("\n") + "\n"
|
|
9011
9064
|
);
|
|
9012
9065
|
}
|
|
9013
|
-
function resolveInstallDestination(args) {
|
|
9014
|
-
if (args.target !== void 0) {
|
|
9015
|
-
throw new Error("--target is no longer supported; pass --config-path and --extensions-dir from your Claw host");
|
|
9016
|
-
}
|
|
9066
|
+
async function resolveInstallDestination(args, options = {}) {
|
|
9017
9067
|
const explicitConfigPath = args["config-path"] ? (0, import_node_path6.resolve)(args["config-path"]) : void 0;
|
|
9018
9068
|
const explicitExtensionsDir = args["extensions-dir"] ? (0, import_node_path6.resolve)(args["extensions-dir"]) : void 0;
|
|
9019
|
-
if (
|
|
9020
|
-
|
|
9069
|
+
if (explicitConfigPath && explicitExtensionsDir) {
|
|
9070
|
+
return {
|
|
9071
|
+
configPath: explicitConfigPath,
|
|
9072
|
+
extensionsDir: explicitExtensionsDir,
|
|
9073
|
+
label: "Claw"
|
|
9074
|
+
};
|
|
9075
|
+
}
|
|
9076
|
+
if (explicitConfigPath || explicitExtensionsDir) {
|
|
9077
|
+
throw new Error("pass --config-path and --extensions-dir together, or omit both to auto-detect Claw");
|
|
9078
|
+
}
|
|
9079
|
+
const detected = detectInstallHosts(options.hostDefinitions ?? getDefaultHostDefinitions());
|
|
9080
|
+
if (detected.length === 1) {
|
|
9081
|
+
return toInstallDestination(detected[0]);
|
|
9082
|
+
}
|
|
9083
|
+
if (detected.length > 1) {
|
|
9084
|
+
const selected = options.selectHost ? await options.selectHost(detected) : await promptForInstallHost(detected, options.ttyPath ?? "/dev/tty");
|
|
9085
|
+
return toInstallDestination(selected);
|
|
9086
|
+
}
|
|
9087
|
+
throw new Error(
|
|
9088
|
+
[
|
|
9089
|
+
"could not auto-detect an installed Claw host.",
|
|
9090
|
+
"Pass --config-path and --extensions-dir to install into a specific Claw.",
|
|
9091
|
+
"",
|
|
9092
|
+
"Supported default locations:",
|
|
9093
|
+
...formatHostList(options.hostDefinitions ?? getDefaultHostDefinitions())
|
|
9094
|
+
].join("\n")
|
|
9095
|
+
);
|
|
9096
|
+
}
|
|
9097
|
+
function getDefaultHostDefinitions() {
|
|
9098
|
+
const home = (0, import_node_os2.homedir)();
|
|
9099
|
+
const qclawHome = (0, import_node_path6.resolve)(home, ".qclaw");
|
|
9100
|
+
const openClawHome = (0, import_node_path6.resolve)(home, ".openclaw");
|
|
9101
|
+
return [
|
|
9102
|
+
{
|
|
9103
|
+
id: "qclaw",
|
|
9104
|
+
label: "QClaw",
|
|
9105
|
+
configPath: (0, import_node_path6.join)(qclawHome, "openclaw.json"),
|
|
9106
|
+
extensionsDir: (0, import_node_path6.resolve)(home, "Library/Application Support/QClaw/openclaw/config/extensions")
|
|
9107
|
+
},
|
|
9108
|
+
{
|
|
9109
|
+
id: "openclaw",
|
|
9110
|
+
label: "OpenClaw",
|
|
9111
|
+
configPath: (0, import_node_path6.join)(openClawHome, "openclaw.json"),
|
|
9112
|
+
extensionsDir: (0, import_node_path6.resolve)(openClawHome, "extensions")
|
|
9113
|
+
}
|
|
9114
|
+
];
|
|
9115
|
+
}
|
|
9116
|
+
function detectInstallHosts(hosts) {
|
|
9117
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9118
|
+
return hosts.filter((host) => {
|
|
9119
|
+
const key = `${(0, import_node_path6.resolve)(host.configPath)}\0${(0, import_node_path6.resolve)(host.extensionsDir)}`;
|
|
9120
|
+
if (seen.has(key) || !(0, import_node_fs4.existsSync)(host.configPath)) {
|
|
9121
|
+
return false;
|
|
9122
|
+
}
|
|
9123
|
+
seen.add(key);
|
|
9124
|
+
return true;
|
|
9125
|
+
});
|
|
9126
|
+
}
|
|
9127
|
+
async function promptForInstallHost(hosts, ttyPath) {
|
|
9128
|
+
let handle;
|
|
9129
|
+
try {
|
|
9130
|
+
handle = await (0, import_promises5.open)(ttyPath, "r+");
|
|
9131
|
+
const input = handle.createReadStream();
|
|
9132
|
+
const output = handle.createWriteStream();
|
|
9133
|
+
const readline = (0, import_promises4.createInterface)({ input, output });
|
|
9134
|
+
try {
|
|
9135
|
+
output.write("Multiple Claw installations were detected.\n");
|
|
9136
|
+
output.write("Choose where to install claw-control-center:\n");
|
|
9137
|
+
hosts.forEach((host, index) => {
|
|
9138
|
+
output.write(`${index + 1}. ${host.label}
|
|
9139
|
+
`);
|
|
9140
|
+
output.write(` Config: ${host.configPath}
|
|
9141
|
+
`);
|
|
9142
|
+
output.write(` Extensions: ${host.extensionsDir}
|
|
9143
|
+
`);
|
|
9144
|
+
});
|
|
9145
|
+
const answer = await readline.question(`Install location [1-${hosts.length}]: `);
|
|
9146
|
+
const selectedIndex = Number(answer.trim());
|
|
9147
|
+
if (!Number.isInteger(selectedIndex) || selectedIndex < 1 || selectedIndex > hosts.length) {
|
|
9148
|
+
throw new Error(`invalid install location: ${answer}`);
|
|
9149
|
+
}
|
|
9150
|
+
return hosts[selectedIndex - 1];
|
|
9151
|
+
} finally {
|
|
9152
|
+
readline.close();
|
|
9153
|
+
input.destroy();
|
|
9154
|
+
output.end();
|
|
9155
|
+
}
|
|
9156
|
+
} catch (error) {
|
|
9157
|
+
if (error instanceof Error && error.message.startsWith("invalid install location:")) {
|
|
9158
|
+
throw error;
|
|
9159
|
+
}
|
|
9160
|
+
throw new Error(
|
|
9161
|
+
[
|
|
9162
|
+
"multiple Claw installations were detected, but no interactive terminal was available.",
|
|
9163
|
+
"Run the installer again with --config-path and --extensions-dir.",
|
|
9164
|
+
"",
|
|
9165
|
+
"Detected locations:",
|
|
9166
|
+
...formatHostList(hosts)
|
|
9167
|
+
].join("\n")
|
|
9168
|
+
);
|
|
9169
|
+
} finally {
|
|
9170
|
+
await handle?.close().catch(() => void 0);
|
|
9021
9171
|
}
|
|
9172
|
+
}
|
|
9173
|
+
function toInstallDestination(host) {
|
|
9022
9174
|
return {
|
|
9023
|
-
configPath:
|
|
9024
|
-
extensionsDir:
|
|
9175
|
+
configPath: (0, import_node_path6.resolve)(host.configPath),
|
|
9176
|
+
extensionsDir: (0, import_node_path6.resolve)(host.extensionsDir),
|
|
9177
|
+
label: host.label
|
|
9025
9178
|
};
|
|
9026
9179
|
}
|
|
9180
|
+
function formatHostList(hosts) {
|
|
9181
|
+
return hosts.flatMap((host) => [
|
|
9182
|
+
`- ${host.label}`,
|
|
9183
|
+
` Config: ${host.configPath}`,
|
|
9184
|
+
` Extensions: ${host.extensionsDir}`
|
|
9185
|
+
]);
|
|
9186
|
+
}
|
|
9027
9187
|
function parseArgs(argv) {
|
|
9028
9188
|
const parsed = {};
|
|
9029
9189
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -9031,12 +9191,19 @@ function parseArgs(argv) {
|
|
|
9031
9191
|
if (!entry.startsWith("--")) {
|
|
9032
9192
|
continue;
|
|
9033
9193
|
}
|
|
9194
|
+
const key = entry.slice(2);
|
|
9195
|
+
if (key === "target") {
|
|
9196
|
+
throw new Error("--target has been removed; omit it to auto-detect Claw or pass --config-path and --extensions-dir");
|
|
9197
|
+
}
|
|
9198
|
+
if (!SUPPORTED_ARGS.has(key)) {
|
|
9199
|
+
throw new Error(`unknown option: --${key}`);
|
|
9200
|
+
}
|
|
9034
9201
|
const next = argv[index + 1];
|
|
9035
9202
|
if (next && !next.startsWith("--")) {
|
|
9036
|
-
parsed[
|
|
9203
|
+
parsed[key] = next;
|
|
9037
9204
|
index += 1;
|
|
9038
9205
|
} else {
|
|
9039
|
-
parsed[
|
|
9206
|
+
parsed[key] = "true";
|
|
9040
9207
|
}
|
|
9041
9208
|
}
|
|
9042
9209
|
return parsed;
|
|
@@ -9051,8 +9218,8 @@ async function copyPublishablePackage(packageRoot, destination) {
|
|
|
9051
9218
|
continue;
|
|
9052
9219
|
}
|
|
9053
9220
|
const target = (0, import_node_path6.join)(destination, relativePath);
|
|
9054
|
-
await (0,
|
|
9055
|
-
await (0,
|
|
9221
|
+
await (0, import_promises5.rm)(target, { recursive: true, force: true });
|
|
9222
|
+
await (0, import_promises5.cp)(source, target, { recursive: true, force: true });
|
|
9056
9223
|
}
|
|
9057
9224
|
}
|
|
9058
9225
|
async function readPluginBuildInfo(destination) {
|
|
@@ -9060,7 +9227,7 @@ async function readPluginBuildInfo(destination) {
|
|
|
9060
9227
|
const entryPath = (0, import_node_path6.join)(destination, "dist", "index.cjs");
|
|
9061
9228
|
let version = "unknown";
|
|
9062
9229
|
try {
|
|
9063
|
-
const packageJson = JSON.parse(await (0,
|
|
9230
|
+
const packageJson = JSON.parse(await (0, import_promises5.readFile)(packagePath, "utf8"));
|
|
9064
9231
|
if (typeof packageJson.version === "string" && packageJson.version.trim()) {
|
|
9065
9232
|
version = packageJson.version.trim();
|
|
9066
9233
|
}
|
|
@@ -9068,7 +9235,7 @@ async function readPluginBuildInfo(destination) {
|
|
|
9068
9235
|
version = "unknown";
|
|
9069
9236
|
}
|
|
9070
9237
|
try {
|
|
9071
|
-
const entry = await (0,
|
|
9238
|
+
const entry = await (0, import_promises5.readFile)(entryPath);
|
|
9072
9239
|
const digest = (0, import_node_crypto4.createHash)("sha256").update(entry).digest("hex").slice(0, 12);
|
|
9073
9240
|
return `${PLUGIN_ID}@${version} sha256:${digest}`;
|
|
9074
9241
|
} catch {
|
|
@@ -9079,7 +9246,7 @@ async function readOpenClawConfig(configPath) {
|
|
|
9079
9246
|
if (!(0, import_node_fs4.existsSync)(configPath)) {
|
|
9080
9247
|
return {};
|
|
9081
9248
|
}
|
|
9082
|
-
return JSON.parse(await (0,
|
|
9249
|
+
return JSON.parse(await (0, import_promises5.readFile)(configPath, "utf8"));
|
|
9083
9250
|
}
|
|
9084
9251
|
function inferGatewaySettings(config) {
|
|
9085
9252
|
const gateway = config.gateway ?? {};
|
package/dist/install-qclaw.cjs
CHANGED
|
@@ -26,12 +26,29 @@ __export(install_qclaw_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(install_qclaw_exports);
|
|
28
28
|
var import_node_crypto = require("crypto");
|
|
29
|
-
var import_promises = require("
|
|
29
|
+
var import_promises = require("readline/promises");
|
|
30
|
+
var import_promises2 = require("fs/promises");
|
|
30
31
|
var import_node_fs = require("fs");
|
|
32
|
+
var import_node_os = require("os");
|
|
31
33
|
var import_node_path = require("path");
|
|
32
34
|
var PLUGIN_ID = "claw-control-center";
|
|
33
35
|
var LEGACY_PLUGIN_ID = "53ai-openclaw";
|
|
34
36
|
var COPY_ITEMS = ["dist", "openclaw.plugin.json", "package.json", "bin", "web-dist"];
|
|
37
|
+
var SUPPORTED_ARGS = /* @__PURE__ */ new Set([
|
|
38
|
+
"gateway",
|
|
39
|
+
"bot-id",
|
|
40
|
+
"secret",
|
|
41
|
+
"prefer-responses-api",
|
|
42
|
+
"gateway-model",
|
|
43
|
+
"hub-ws-url",
|
|
44
|
+
"hub-bot-id",
|
|
45
|
+
"hub-secret",
|
|
46
|
+
"hub-enabled",
|
|
47
|
+
"extensions-dir",
|
|
48
|
+
"config-path",
|
|
49
|
+
"console-host",
|
|
50
|
+
"console-port"
|
|
51
|
+
]);
|
|
35
52
|
async function installIntoQClaw(input) {
|
|
36
53
|
return installIntoHost(input, "QClaw");
|
|
37
54
|
}
|
|
@@ -39,19 +56,21 @@ async function installIntoOpenClaw(input) {
|
|
|
39
56
|
return installIntoHost(input, "OpenClaw");
|
|
40
57
|
}
|
|
41
58
|
async function installIntoHost(input, hostLabel) {
|
|
42
|
-
await (0,
|
|
59
|
+
await (0, import_promises2.mkdir)(input.extensionsDir, { recursive: true });
|
|
43
60
|
const destination = (0, import_node_path.join)(input.extensionsDir, PLUGIN_ID);
|
|
44
|
-
await (0,
|
|
61
|
+
await (0, import_promises2.mkdir)(destination, { recursive: true });
|
|
45
62
|
await copyPublishablePackage(input.packageRoot, destination);
|
|
46
|
-
await (0,
|
|
63
|
+
await (0, import_promises2.mkdir)((0, import_node_path.dirname)(input.configPath), { recursive: true });
|
|
47
64
|
const config = await readOpenClawConfig(input.configPath);
|
|
48
65
|
const inferredGateway = inferGatewaySettings(config);
|
|
49
66
|
const inferredHub53AI = inferHub53AISettings(config);
|
|
50
|
-
const
|
|
67
|
+
const explicitGatewayBaseUrl = input.gateway?.trim();
|
|
68
|
+
const explicitGatewaySecret = input.secret?.trim();
|
|
69
|
+
const gatewayBaseUrl = explicitGatewayBaseUrl || inferredGateway.baseUrl;
|
|
51
70
|
if (!gatewayBaseUrl) {
|
|
52
71
|
throw new Error(`missing gateway URL and no local ${hostLabel} gateway could be inferred`);
|
|
53
72
|
}
|
|
54
|
-
const secret =
|
|
73
|
+
const secret = explicitGatewaySecret || inferredGateway.secret;
|
|
55
74
|
if (!secret) {
|
|
56
75
|
throw new Error(`missing gateway secret and no local ${hostLabel} gateway token could be inferred`);
|
|
57
76
|
}
|
|
@@ -86,8 +105,16 @@ async function installIntoHost(input, hostLabel) {
|
|
|
86
105
|
const previousEntry = ensureObject(entries, PLUGIN_ID);
|
|
87
106
|
const previousConfig = ensureObject(previousEntry, "config");
|
|
88
107
|
const previousGateway = ensureObject(previousConfig, "gateway");
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
if (explicitGatewayBaseUrl) {
|
|
109
|
+
previousGateway.baseUrl = explicitGatewayBaseUrl;
|
|
110
|
+
} else {
|
|
111
|
+
delete previousGateway.baseUrl;
|
|
112
|
+
}
|
|
113
|
+
if (explicitGatewaySecret) {
|
|
114
|
+
previousGateway.secret = explicitGatewaySecret;
|
|
115
|
+
} else {
|
|
116
|
+
delete previousGateway.secret;
|
|
117
|
+
}
|
|
91
118
|
previousGateway.preferResponsesApi = preferResponsesApi;
|
|
92
119
|
if (botId) {
|
|
93
120
|
previousGateway.botId = botId;
|
|
@@ -128,7 +155,7 @@ async function installIntoHost(input, hostLabel) {
|
|
|
128
155
|
}
|
|
129
156
|
}
|
|
130
157
|
previousEntry.enabled = true;
|
|
131
|
-
await (0,
|
|
158
|
+
await (0, import_promises2.writeFile)(input.configPath, `${JSON.stringify(config, null, 2)}
|
|
132
159
|
`);
|
|
133
160
|
return {
|
|
134
161
|
configPath: input.configPath,
|
|
@@ -145,7 +172,11 @@ async function runInstallCommand(input) {
|
|
|
145
172
|
throw new Error("expected subcommand: install");
|
|
146
173
|
}
|
|
147
174
|
const args = parseArgs(argv.slice(1));
|
|
148
|
-
const destination = resolveInstallDestination(args
|
|
175
|
+
const destination = await resolveInstallDestination(args, {
|
|
176
|
+
hostDefinitions: input.hostDefinitions,
|
|
177
|
+
selectHost: input.selectHost,
|
|
178
|
+
ttyPath: input.ttyPath
|
|
179
|
+
});
|
|
149
180
|
const result = await installIntoHost(
|
|
150
181
|
{
|
|
151
182
|
packageRoot: input.packageRoot,
|
|
@@ -163,34 +194,141 @@ async function runInstallCommand(input) {
|
|
|
163
194
|
consoleHost: args["console-host"],
|
|
164
195
|
consolePort: args["console-port"] ? Number(args["console-port"]) : void 0
|
|
165
196
|
},
|
|
166
|
-
|
|
197
|
+
destination.label
|
|
167
198
|
);
|
|
168
199
|
process.stdout.write(
|
|
169
200
|
[
|
|
170
|
-
`Installed ${PLUGIN_ID} into
|
|
201
|
+
`Installed ${PLUGIN_ID} into ${destination.label}.`,
|
|
171
202
|
`Extensions: ${result.extensionsDir}`,
|
|
172
203
|
`Config: ${result.configPath}`,
|
|
173
204
|
`Gateway: ${result.gatewayBaseUrl}`,
|
|
174
205
|
`53AIHub: ${result.hub53aiConfigured ? "configured" : "not configured"}`,
|
|
175
206
|
`Plugin build: ${result.pluginBuild}`,
|
|
176
|
-
|
|
207
|
+
`Restart ${destination.label} to load the plugin.`
|
|
177
208
|
].join("\n") + "\n"
|
|
178
209
|
);
|
|
179
210
|
}
|
|
180
|
-
function resolveInstallDestination(args) {
|
|
181
|
-
if (args.target !== void 0) {
|
|
182
|
-
throw new Error("--target is no longer supported; pass --config-path and --extensions-dir from your Claw host");
|
|
183
|
-
}
|
|
211
|
+
async function resolveInstallDestination(args, options = {}) {
|
|
184
212
|
const explicitConfigPath = args["config-path"] ? (0, import_node_path.resolve)(args["config-path"]) : void 0;
|
|
185
213
|
const explicitExtensionsDir = args["extensions-dir"] ? (0, import_node_path.resolve)(args["extensions-dir"]) : void 0;
|
|
186
|
-
if (
|
|
187
|
-
|
|
214
|
+
if (explicitConfigPath && explicitExtensionsDir) {
|
|
215
|
+
return {
|
|
216
|
+
configPath: explicitConfigPath,
|
|
217
|
+
extensionsDir: explicitExtensionsDir,
|
|
218
|
+
label: "Claw"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (explicitConfigPath || explicitExtensionsDir) {
|
|
222
|
+
throw new Error("pass --config-path and --extensions-dir together, or omit both to auto-detect Claw");
|
|
223
|
+
}
|
|
224
|
+
const detected = detectInstallHosts(options.hostDefinitions ?? getDefaultHostDefinitions());
|
|
225
|
+
if (detected.length === 1) {
|
|
226
|
+
return toInstallDestination(detected[0]);
|
|
227
|
+
}
|
|
228
|
+
if (detected.length > 1) {
|
|
229
|
+
const selected = options.selectHost ? await options.selectHost(detected) : await promptForInstallHost(detected, options.ttyPath ?? "/dev/tty");
|
|
230
|
+
return toInstallDestination(selected);
|
|
188
231
|
}
|
|
232
|
+
throw new Error(
|
|
233
|
+
[
|
|
234
|
+
"could not auto-detect an installed Claw host.",
|
|
235
|
+
"Pass --config-path and --extensions-dir to install into a specific Claw.",
|
|
236
|
+
"",
|
|
237
|
+
"Supported default locations:",
|
|
238
|
+
...formatHostList(options.hostDefinitions ?? getDefaultHostDefinitions())
|
|
239
|
+
].join("\n")
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
function getDefaultHostDefinitions() {
|
|
243
|
+
const home = (0, import_node_os.homedir)();
|
|
244
|
+
const qclawHome = (0, import_node_path.resolve)(home, ".qclaw");
|
|
245
|
+
const openClawHome = (0, import_node_path.resolve)(home, ".openclaw");
|
|
246
|
+
return [
|
|
247
|
+
{
|
|
248
|
+
id: "qclaw",
|
|
249
|
+
label: "QClaw",
|
|
250
|
+
configPath: (0, import_node_path.join)(qclawHome, "openclaw.json"),
|
|
251
|
+
extensionsDir: (0, import_node_path.resolve)(home, "Library/Application Support/QClaw/openclaw/config/extensions")
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: "openclaw",
|
|
255
|
+
label: "OpenClaw",
|
|
256
|
+
configPath: (0, import_node_path.join)(openClawHome, "openclaw.json"),
|
|
257
|
+
extensionsDir: (0, import_node_path.resolve)(openClawHome, "extensions")
|
|
258
|
+
}
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
function detectInstallHosts(hosts) {
|
|
262
|
+
const seen = /* @__PURE__ */ new Set();
|
|
263
|
+
return hosts.filter((host) => {
|
|
264
|
+
const key = `${(0, import_node_path.resolve)(host.configPath)}\0${(0, import_node_path.resolve)(host.extensionsDir)}`;
|
|
265
|
+
if (seen.has(key) || !(0, import_node_fs.existsSync)(host.configPath)) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
seen.add(key);
|
|
269
|
+
return true;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
async function promptForInstallHost(hosts, ttyPath) {
|
|
273
|
+
let handle;
|
|
274
|
+
try {
|
|
275
|
+
handle = await (0, import_promises2.open)(ttyPath, "r+");
|
|
276
|
+
const input = handle.createReadStream();
|
|
277
|
+
const output = handle.createWriteStream();
|
|
278
|
+
const readline = (0, import_promises.createInterface)({ input, output });
|
|
279
|
+
try {
|
|
280
|
+
output.write("Multiple Claw installations were detected.\n");
|
|
281
|
+
output.write("Choose where to install claw-control-center:\n");
|
|
282
|
+
hosts.forEach((host, index) => {
|
|
283
|
+
output.write(`${index + 1}. ${host.label}
|
|
284
|
+
`);
|
|
285
|
+
output.write(` Config: ${host.configPath}
|
|
286
|
+
`);
|
|
287
|
+
output.write(` Extensions: ${host.extensionsDir}
|
|
288
|
+
`);
|
|
289
|
+
});
|
|
290
|
+
const answer = await readline.question(`Install location [1-${hosts.length}]: `);
|
|
291
|
+
const selectedIndex = Number(answer.trim());
|
|
292
|
+
if (!Number.isInteger(selectedIndex) || selectedIndex < 1 || selectedIndex > hosts.length) {
|
|
293
|
+
throw new Error(`invalid install location: ${answer}`);
|
|
294
|
+
}
|
|
295
|
+
return hosts[selectedIndex - 1];
|
|
296
|
+
} finally {
|
|
297
|
+
readline.close();
|
|
298
|
+
input.destroy();
|
|
299
|
+
output.end();
|
|
300
|
+
}
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (error instanceof Error && error.message.startsWith("invalid install location:")) {
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
throw new Error(
|
|
306
|
+
[
|
|
307
|
+
"multiple Claw installations were detected, but no interactive terminal was available.",
|
|
308
|
+
"Run the installer again with --config-path and --extensions-dir.",
|
|
309
|
+
"",
|
|
310
|
+
"Detected locations:",
|
|
311
|
+
...formatHostList(hosts)
|
|
312
|
+
].join("\n")
|
|
313
|
+
);
|
|
314
|
+
} finally {
|
|
315
|
+
await handle?.close().catch(() => void 0);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function toInstallDestination(host) {
|
|
189
319
|
return {
|
|
190
|
-
configPath:
|
|
191
|
-
extensionsDir:
|
|
320
|
+
configPath: (0, import_node_path.resolve)(host.configPath),
|
|
321
|
+
extensionsDir: (0, import_node_path.resolve)(host.extensionsDir),
|
|
322
|
+
label: host.label
|
|
192
323
|
};
|
|
193
324
|
}
|
|
325
|
+
function formatHostList(hosts) {
|
|
326
|
+
return hosts.flatMap((host) => [
|
|
327
|
+
`- ${host.label}`,
|
|
328
|
+
` Config: ${host.configPath}`,
|
|
329
|
+
` Extensions: ${host.extensionsDir}`
|
|
330
|
+
]);
|
|
331
|
+
}
|
|
194
332
|
function parseArgs(argv) {
|
|
195
333
|
const parsed = {};
|
|
196
334
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -198,12 +336,19 @@ function parseArgs(argv) {
|
|
|
198
336
|
if (!entry.startsWith("--")) {
|
|
199
337
|
continue;
|
|
200
338
|
}
|
|
339
|
+
const key = entry.slice(2);
|
|
340
|
+
if (key === "target") {
|
|
341
|
+
throw new Error("--target has been removed; omit it to auto-detect Claw or pass --config-path and --extensions-dir");
|
|
342
|
+
}
|
|
343
|
+
if (!SUPPORTED_ARGS.has(key)) {
|
|
344
|
+
throw new Error(`unknown option: --${key}`);
|
|
345
|
+
}
|
|
201
346
|
const next = argv[index + 1];
|
|
202
347
|
if (next && !next.startsWith("--")) {
|
|
203
|
-
parsed[
|
|
348
|
+
parsed[key] = next;
|
|
204
349
|
index += 1;
|
|
205
350
|
} else {
|
|
206
|
-
parsed[
|
|
351
|
+
parsed[key] = "true";
|
|
207
352
|
}
|
|
208
353
|
}
|
|
209
354
|
return parsed;
|
|
@@ -218,8 +363,8 @@ async function copyPublishablePackage(packageRoot, destination) {
|
|
|
218
363
|
continue;
|
|
219
364
|
}
|
|
220
365
|
const target = (0, import_node_path.join)(destination, relativePath);
|
|
221
|
-
await (0,
|
|
222
|
-
await (0,
|
|
366
|
+
await (0, import_promises2.rm)(target, { recursive: true, force: true });
|
|
367
|
+
await (0, import_promises2.cp)(source, target, { recursive: true, force: true });
|
|
223
368
|
}
|
|
224
369
|
}
|
|
225
370
|
async function readPluginBuildInfo(destination) {
|
|
@@ -227,7 +372,7 @@ async function readPluginBuildInfo(destination) {
|
|
|
227
372
|
const entryPath = (0, import_node_path.join)(destination, "dist", "index.cjs");
|
|
228
373
|
let version = "unknown";
|
|
229
374
|
try {
|
|
230
|
-
const packageJson = JSON.parse(await (0,
|
|
375
|
+
const packageJson = JSON.parse(await (0, import_promises2.readFile)(packagePath, "utf8"));
|
|
231
376
|
if (typeof packageJson.version === "string" && packageJson.version.trim()) {
|
|
232
377
|
version = packageJson.version.trim();
|
|
233
378
|
}
|
|
@@ -235,7 +380,7 @@ async function readPluginBuildInfo(destination) {
|
|
|
235
380
|
version = "unknown";
|
|
236
381
|
}
|
|
237
382
|
try {
|
|
238
|
-
const entry = await (0,
|
|
383
|
+
const entry = await (0, import_promises2.readFile)(entryPath);
|
|
239
384
|
const digest = (0, import_node_crypto.createHash)("sha256").update(entry).digest("hex").slice(0, 12);
|
|
240
385
|
return `${PLUGIN_ID}@${version} sha256:${digest}`;
|
|
241
386
|
} catch {
|
|
@@ -246,7 +391,7 @@ async function readOpenClawConfig(configPath) {
|
|
|
246
391
|
if (!(0, import_node_fs.existsSync)(configPath)) {
|
|
247
392
|
return {};
|
|
248
393
|
}
|
|
249
|
-
return JSON.parse(await (0,
|
|
394
|
+
return JSON.parse(await (0, import_promises2.readFile)(configPath, "utf8"));
|
|
250
395
|
}
|
|
251
396
|
function inferGatewaySettings(config) {
|
|
252
397
|
const gateway = config.gateway ?? {};
|
package/dist/install-qclaw.d.cts
CHANGED
|
@@ -14,6 +14,13 @@ type InstallInput = {
|
|
|
14
14
|
consoleHost?: string;
|
|
15
15
|
consolePort?: number;
|
|
16
16
|
};
|
|
17
|
+
type InstallLabel = "Claw" | "QClaw" | "OpenClaw";
|
|
18
|
+
type HostDefinition = {
|
|
19
|
+
id: string;
|
|
20
|
+
label: InstallLabel;
|
|
21
|
+
configPath: string;
|
|
22
|
+
extensionsDir: string;
|
|
23
|
+
};
|
|
17
24
|
declare function installIntoQClaw(input: InstallInput): Promise<{
|
|
18
25
|
configPath: string;
|
|
19
26
|
extensionsDir: string;
|
|
@@ -33,6 +40,9 @@ declare function installIntoOpenClaw(input: InstallInput): Promise<{
|
|
|
33
40
|
declare function runInstallCommand(input: {
|
|
34
41
|
argv?: string[];
|
|
35
42
|
packageRoot: string;
|
|
43
|
+
hostDefinitions?: HostDefinition[];
|
|
44
|
+
selectHost?: (hosts: HostDefinition[]) => Promise<HostDefinition>;
|
|
45
|
+
ttyPath?: string;
|
|
36
46
|
}): Promise<void>;
|
|
37
47
|
|
|
38
|
-
export { installIntoOpenClaw, installIntoQClaw, runInstallCommand };
|
|
48
|
+
export { type HostDefinition, installIntoOpenClaw, installIntoQClaw, runInstallCommand };
|