claw-control-center 0.1.0
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/bin/install-qclaw.mjs +15 -0
- package/dist/index.cjs +2676 -0
- package/dist/index.d.cts +181 -0
- package/dist/install-qclaw.cjs +288 -0
- package/dist/install-qclaw.d.cts +34 -0
- package/openclaw.plugin.json +130 -0
- package/package.json +39 -0
- package/web-dist/assets/index-CPMfZC6Y.css +1 -0
- package/web-dist/assets/index-_bUpQoIN.js +9 -0
- package/web-dist/index.html +13 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
2
|
+
export { installIntoOpenClaw, installIntoQClaw, runInstallCommand } from './install-qclaw.cjs';
|
|
3
|
+
|
|
4
|
+
type SessionStatus = "idle" | "running" | "completed" | "failed" | "interrupted" | "archived";
|
|
5
|
+
type SessionSummary = {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
status: SessionStatus;
|
|
9
|
+
hostKind: string;
|
|
10
|
+
runnerCommand: string;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
lastEventSeq: number;
|
|
14
|
+
};
|
|
15
|
+
type SessionMessage = {
|
|
16
|
+
id: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
role: string;
|
|
19
|
+
content: string;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
};
|
|
22
|
+
type TimelineEvent = {
|
|
23
|
+
id: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
seq: number;
|
|
26
|
+
kind: string;
|
|
27
|
+
payload: Record<string, unknown>;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
};
|
|
30
|
+
type ControlAction = "stop" | "retry" | "rename" | "archive";
|
|
31
|
+
|
|
32
|
+
type GatewayConfig = {
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
botId: string;
|
|
35
|
+
secret: string;
|
|
36
|
+
requestTimeoutMs: number;
|
|
37
|
+
streamReconnectMs: number;
|
|
38
|
+
runtimeRoot?: string;
|
|
39
|
+
};
|
|
40
|
+
type GatewaySession = SessionSummary;
|
|
41
|
+
type GatewayEvent = TimelineEvent;
|
|
42
|
+
type GatewayClient = {
|
|
43
|
+
listSessions(limit?: number): Promise<GatewaySession[]>;
|
|
44
|
+
createSession(title: string, initialPrompt?: string): Promise<GatewaySession>;
|
|
45
|
+
getSession(sessionId: string): Promise<GatewaySession>;
|
|
46
|
+
getSessionMessages(sessionId: string, limit?: number): Promise<SessionMessage[]>;
|
|
47
|
+
sendMessage(sessionId: string, content: string): Promise<void>;
|
|
48
|
+
controlSession(sessionId: string, action: ControlAction, title?: string): Promise<void>;
|
|
49
|
+
listEvents(sessionId: string, afterSeq?: number): Promise<GatewayEvent[]>;
|
|
50
|
+
subscribe(sessionId: string, afterSeq: number, handlers: {
|
|
51
|
+
onEvent: (event: GatewayEvent) => void;
|
|
52
|
+
onDisconnect: (error?: Error) => void;
|
|
53
|
+
}): () => void;
|
|
54
|
+
stop(): Promise<void>;
|
|
55
|
+
};
|
|
56
|
+
declare function createGatewayClient(config: Partial<GatewayConfig>): GatewayClient;
|
|
57
|
+
|
|
58
|
+
type Hub53AIConfig = {
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
botId: string;
|
|
61
|
+
secret: string;
|
|
62
|
+
wsUrl: string;
|
|
63
|
+
accessPolicy: "open" | "allowlist";
|
|
64
|
+
allowFrom: string[];
|
|
65
|
+
sendThinkingMessage: boolean;
|
|
66
|
+
reconnectBaseMs: number;
|
|
67
|
+
maxReconnectAttempts: number;
|
|
68
|
+
};
|
|
69
|
+
type Hub53AIStatusSnapshot = {
|
|
70
|
+
enabled: boolean;
|
|
71
|
+
configured: boolean;
|
|
72
|
+
connectionStatus: "disabled" | "connecting" | "connected" | "disconnected" | "error";
|
|
73
|
+
botId?: string;
|
|
74
|
+
wsUrl?: string;
|
|
75
|
+
lastHeartbeatAt?: string;
|
|
76
|
+
lastConnectedAt?: string;
|
|
77
|
+
lastError?: string;
|
|
78
|
+
receivedMessageCount: number;
|
|
79
|
+
sentMessageCount: number;
|
|
80
|
+
pendingOutboundCount: number;
|
|
81
|
+
};
|
|
82
|
+
type Hub53AIIncomingMessage = {
|
|
83
|
+
type: string;
|
|
84
|
+
msgId: string;
|
|
85
|
+
reqId: string;
|
|
86
|
+
chatId: string;
|
|
87
|
+
userId: string;
|
|
88
|
+
text: string;
|
|
89
|
+
imageUrls?: string[];
|
|
90
|
+
fileUrls?: string[];
|
|
91
|
+
quoteContent?: string;
|
|
92
|
+
};
|
|
93
|
+
type Hub53AIOutgoingChunk = {
|
|
94
|
+
req_id: string;
|
|
95
|
+
action: "chat";
|
|
96
|
+
status: "streaming" | "thinking" | "done" | "error";
|
|
97
|
+
data: {
|
|
98
|
+
id: string;
|
|
99
|
+
object: "chat.completion.chunk";
|
|
100
|
+
created: number;
|
|
101
|
+
model: "openclaw-agent";
|
|
102
|
+
choices: Array<{
|
|
103
|
+
index: number;
|
|
104
|
+
delta: {
|
|
105
|
+
content: string;
|
|
106
|
+
role: "assistant";
|
|
107
|
+
};
|
|
108
|
+
finish_reason: "stop" | "error" | null;
|
|
109
|
+
}>;
|
|
110
|
+
error?: {
|
|
111
|
+
code: string;
|
|
112
|
+
message: string;
|
|
113
|
+
details?: string;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
type HubBridgeCallbacks = {
|
|
118
|
+
onSessionUpsert(session: GatewaySession): Promise<void>;
|
|
119
|
+
onUserMessage(message: SessionMessage): Promise<void>;
|
|
120
|
+
onSessionStatus(sessionId: string, status: SessionStatus): Promise<void>;
|
|
121
|
+
onEnsureSessionStream(sessionId: string): Promise<void>;
|
|
122
|
+
getLastEventSeq(sessionId: string): number;
|
|
123
|
+
onStatusChange(): void;
|
|
124
|
+
};
|
|
125
|
+
type HubBridgeInput = {
|
|
126
|
+
stateDir: string;
|
|
127
|
+
config: Hub53AIConfig;
|
|
128
|
+
gateway: GatewayClient;
|
|
129
|
+
callbacks: HubBridgeCallbacks;
|
|
130
|
+
logger?: {
|
|
131
|
+
info?(message: string): void;
|
|
132
|
+
warn?(message: string): void;
|
|
133
|
+
error?(message: string): void;
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
declare function createHub53AIBridge(input: HubBridgeInput): {
|
|
137
|
+
start: () => Promise<void>;
|
|
138
|
+
stop: () => Promise<void>;
|
|
139
|
+
getStatus: () => Hub53AIStatusSnapshot;
|
|
140
|
+
};
|
|
141
|
+
declare function parseIncomingMessage(rawJson: string): Hub53AIIncomingMessage | null;
|
|
142
|
+
|
|
143
|
+
type HostRuntimeInfo = {
|
|
144
|
+
modelPrimary?: string;
|
|
145
|
+
enabledSkills: string[];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
type ConsoleConfig = {
|
|
149
|
+
host: string;
|
|
150
|
+
port: number;
|
|
151
|
+
};
|
|
152
|
+
type PersistenceConfig = {
|
|
153
|
+
maxSessions: number;
|
|
154
|
+
};
|
|
155
|
+
type CreateConsoleServerInput = {
|
|
156
|
+
stateDir: string;
|
|
157
|
+
configPath: string;
|
|
158
|
+
hostKind: string;
|
|
159
|
+
pluginVersion: string;
|
|
160
|
+
token: string;
|
|
161
|
+
gatewayConfig: GatewayConfig;
|
|
162
|
+
hub53aiConfig?: Hub53AIConfig;
|
|
163
|
+
consoleConfig: ConsoleConfig;
|
|
164
|
+
persistence: PersistenceConfig;
|
|
165
|
+
hostRuntime?: HostRuntimeInfo;
|
|
166
|
+
gateway: GatewayClient;
|
|
167
|
+
webDir?: string;
|
|
168
|
+
};
|
|
169
|
+
declare function createConsoleServer(input: CreateConsoleServerInput): {
|
|
170
|
+
start: () => Promise<void>;
|
|
171
|
+
stop: () => Promise<void>;
|
|
172
|
+
readonly baseUrl: string;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
declare const plugin: {
|
|
176
|
+
id: string;
|
|
177
|
+
name: string;
|
|
178
|
+
register(api: OpenClawPluginApi): void;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export { type Hub53AIConfig, type Hub53AIIncomingMessage, type Hub53AIOutgoingChunk, type Hub53AIStatusSnapshot, createConsoleServer, createGatewayClient, createHub53AIBridge, plugin as default, parseIncomingMessage as parseHub53AIIncomingMessage };
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/install-qclaw.ts
|
|
21
|
+
var install_qclaw_exports = {};
|
|
22
|
+
__export(install_qclaw_exports, {
|
|
23
|
+
installIntoOpenClaw: () => installIntoOpenClaw,
|
|
24
|
+
installIntoQClaw: () => installIntoQClaw,
|
|
25
|
+
runInstallCommand: () => runInstallCommand
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(install_qclaw_exports);
|
|
28
|
+
var import_promises = require("fs/promises");
|
|
29
|
+
var import_node_fs = require("fs");
|
|
30
|
+
var import_node_os = require("os");
|
|
31
|
+
var import_node_path = require("path");
|
|
32
|
+
var PLUGIN_ID = "claw-control-center";
|
|
33
|
+
var DEFAULT_QCLAW_HOME = (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".qclaw");
|
|
34
|
+
var DEFAULT_OPENCLAW_HOME = (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".openclaw");
|
|
35
|
+
var DEFAULT_EXTENSIONS_DIR = (0, import_node_path.resolve)(
|
|
36
|
+
(0, import_node_os.homedir)(),
|
|
37
|
+
"Library/Application Support/QClaw/openclaw/config/extensions"
|
|
38
|
+
);
|
|
39
|
+
var DEFAULT_OPENCLAW_EXTENSIONS_DIR = (0, import_node_path.resolve)(DEFAULT_OPENCLAW_HOME, "extensions");
|
|
40
|
+
var COPY_ITEMS = ["dist", "openclaw.plugin.json", "package.json", "bin", "web-dist"];
|
|
41
|
+
var INSTALL_TARGETS = {
|
|
42
|
+
qclaw: {
|
|
43
|
+
label: "QClaw",
|
|
44
|
+
defaultConfigPath: (0, import_node_path.join)(DEFAULT_QCLAW_HOME, "openclaw.json"),
|
|
45
|
+
defaultExtensionsDir: DEFAULT_EXTENSIONS_DIR
|
|
46
|
+
},
|
|
47
|
+
openclaw: {
|
|
48
|
+
label: "OpenClaw",
|
|
49
|
+
defaultConfigPath: (0, import_node_path.join)(DEFAULT_OPENCLAW_HOME, "openclaw.json"),
|
|
50
|
+
defaultExtensionsDir: DEFAULT_OPENCLAW_EXTENSIONS_DIR
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
async function installIntoQClaw(input) {
|
|
54
|
+
return installIntoHost(input, "QClaw");
|
|
55
|
+
}
|
|
56
|
+
async function installIntoOpenClaw(input) {
|
|
57
|
+
return installIntoHost(input, "OpenClaw");
|
|
58
|
+
}
|
|
59
|
+
async function installIntoHost(input, hostLabel) {
|
|
60
|
+
await (0, import_promises.mkdir)(input.extensionsDir, { recursive: true });
|
|
61
|
+
const destination = (0, import_node_path.join)(input.extensionsDir, PLUGIN_ID);
|
|
62
|
+
await (0, import_promises.mkdir)(destination, { recursive: true });
|
|
63
|
+
await copyPublishablePackage(input.packageRoot, destination);
|
|
64
|
+
await (0, import_promises.mkdir)((0, import_node_path.dirname)(input.configPath), { recursive: true });
|
|
65
|
+
const config = await readOpenClawConfig(input.configPath);
|
|
66
|
+
const inferredGateway = inferGatewaySettings(config);
|
|
67
|
+
const inferredHub53AI = inferHub53AISettings(config);
|
|
68
|
+
const gatewayBaseUrl = input.gateway?.trim() || inferredGateway.baseUrl;
|
|
69
|
+
if (!gatewayBaseUrl) {
|
|
70
|
+
throw new Error(`missing gateway URL and no local ${hostLabel} gateway could be inferred`);
|
|
71
|
+
}
|
|
72
|
+
const secret = input.secret?.trim() || inferredGateway.secret;
|
|
73
|
+
if (!secret) {
|
|
74
|
+
throw new Error(`missing gateway secret and no local ${hostLabel} gateway token could be inferred`);
|
|
75
|
+
}
|
|
76
|
+
const botId = input.botId?.trim();
|
|
77
|
+
const hubWsUrl = input.hubWsUrl?.trim() || inferredHub53AI.wsUrl;
|
|
78
|
+
const hubBotId = input.hubBotId?.trim() || inferredHub53AI.botId;
|
|
79
|
+
const hubSecret = input.hubSecret?.trim() || inferredHub53AI.secret;
|
|
80
|
+
const hubConfigured = Boolean(hubWsUrl && hubBotId && hubSecret);
|
|
81
|
+
const hubInputProvided = Boolean(input.hubWsUrl?.trim() || input.hubBotId?.trim() || input.hubSecret?.trim());
|
|
82
|
+
const hubEnabled = input.hubEnabled ?? (hubConfigured ? hubInputProvided ? true : inferredHub53AI.enabled : false);
|
|
83
|
+
if (hubEnabled && !hubConfigured) {
|
|
84
|
+
throw new Error("hub53ai requires --hub-ws-url, --hub-bot-id, and --hub-secret when enabled");
|
|
85
|
+
}
|
|
86
|
+
const consoleHost = input.consoleHost?.trim();
|
|
87
|
+
const consolePort = normalizePort(input.consolePort);
|
|
88
|
+
const plugins = ensureObject(config, "plugins");
|
|
89
|
+
plugins.enabled = true;
|
|
90
|
+
plugins.allow = dedupeStrings([...Array.isArray(plugins.allow) ? plugins.allow : [], PLUGIN_ID]);
|
|
91
|
+
const load = ensureObject(plugins, "load");
|
|
92
|
+
load.paths = dedupeStrings([...Array.isArray(load.paths) ? load.paths : [], input.extensionsDir]);
|
|
93
|
+
const entries = ensureObject(plugins, "entries");
|
|
94
|
+
const previousEntry = ensureObject(entries, PLUGIN_ID);
|
|
95
|
+
const previousConfig = ensureObject(previousEntry, "config");
|
|
96
|
+
const previousGateway = ensureObject(previousConfig, "gateway");
|
|
97
|
+
previousGateway.baseUrl = gatewayBaseUrl;
|
|
98
|
+
previousGateway.secret = secret;
|
|
99
|
+
if (botId) {
|
|
100
|
+
previousGateway.botId = botId;
|
|
101
|
+
}
|
|
102
|
+
if (hubConfigured || input.hubEnabled !== void 0) {
|
|
103
|
+
const previousHub = ensureObject(previousConfig, "hub53ai");
|
|
104
|
+
previousHub.enabled = hubEnabled;
|
|
105
|
+
if (hubBotId) {
|
|
106
|
+
previousHub.botId = hubBotId;
|
|
107
|
+
}
|
|
108
|
+
if (hubSecret) {
|
|
109
|
+
previousHub.secret = hubSecret;
|
|
110
|
+
}
|
|
111
|
+
if (hubWsUrl) {
|
|
112
|
+
previousHub.wsUrl = hubWsUrl;
|
|
113
|
+
}
|
|
114
|
+
previousHub.accessPolicy = inferredHub53AI.accessPolicy || previousHub.accessPolicy || "open";
|
|
115
|
+
previousHub.allowFrom = inferredHub53AI.allowFrom ?? previousHub.allowFrom ?? [];
|
|
116
|
+
previousHub.sendThinkingMessage = inferredHub53AI.sendThinkingMessage ?? previousHub.sendThinkingMessage ?? true;
|
|
117
|
+
}
|
|
118
|
+
if (consoleHost || consolePort !== void 0) {
|
|
119
|
+
const previousConsole = ensureObject(previousConfig, "console");
|
|
120
|
+
if (consoleHost) {
|
|
121
|
+
previousConsole.host = consoleHost;
|
|
122
|
+
}
|
|
123
|
+
if (consolePort !== void 0) {
|
|
124
|
+
previousConsole.port = consolePort;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
previousEntry.enabled = true;
|
|
128
|
+
await (0, import_promises.writeFile)(input.configPath, `${JSON.stringify(config, null, 2)}
|
|
129
|
+
`);
|
|
130
|
+
return {
|
|
131
|
+
configPath: input.configPath,
|
|
132
|
+
extensionsDir: input.extensionsDir,
|
|
133
|
+
destination,
|
|
134
|
+
gatewayBaseUrl,
|
|
135
|
+
hub53aiConfigured: hubConfigured
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function runInstallCommand(input) {
|
|
139
|
+
const argv = input.argv ?? process.argv.slice(2);
|
|
140
|
+
if (argv[0] !== "install") {
|
|
141
|
+
throw new Error("expected subcommand: install");
|
|
142
|
+
}
|
|
143
|
+
const args = parseArgs(argv.slice(1));
|
|
144
|
+
const target = parseInstallTarget(args.target);
|
|
145
|
+
if (!target) {
|
|
146
|
+
throw new Error("expected --target qclaw or --target openclaw");
|
|
147
|
+
}
|
|
148
|
+
const targetInfo = INSTALL_TARGETS[target];
|
|
149
|
+
const configPath = (0, import_node_path.resolve)(args["config-path"] ?? targetInfo.defaultConfigPath);
|
|
150
|
+
const extensionsDir = (0, import_node_path.resolve)(args["extensions-dir"] ?? targetInfo.defaultExtensionsDir);
|
|
151
|
+
const install = target === "openclaw" ? installIntoOpenClaw : installIntoQClaw;
|
|
152
|
+
const result = await install({
|
|
153
|
+
packageRoot: input.packageRoot,
|
|
154
|
+
extensionsDir,
|
|
155
|
+
configPath,
|
|
156
|
+
gateway: args.gateway,
|
|
157
|
+
botId: args["bot-id"],
|
|
158
|
+
secret: args.secret,
|
|
159
|
+
hubWsUrl: args["hub-ws-url"],
|
|
160
|
+
hubBotId: args["hub-bot-id"],
|
|
161
|
+
hubSecret: args["hub-secret"],
|
|
162
|
+
hubEnabled: parseOptionalBoolean(args["hub-enabled"]),
|
|
163
|
+
consoleHost: args["console-host"],
|
|
164
|
+
consolePort: args["console-port"] ? Number(args["console-port"]) : void 0
|
|
165
|
+
});
|
|
166
|
+
process.stdout.write(
|
|
167
|
+
[
|
|
168
|
+
`Installed ${PLUGIN_ID} into ${targetInfo.label}.`,
|
|
169
|
+
`Extensions: ${result.extensionsDir}`,
|
|
170
|
+
`Config: ${result.configPath}`,
|
|
171
|
+
`Gateway: ${result.gatewayBaseUrl}`,
|
|
172
|
+
`53AIHub: ${result.hub53aiConfigured ? "configured" : "not configured"}`,
|
|
173
|
+
`Restart ${targetInfo.label} to load the plugin.`
|
|
174
|
+
].join("\n") + "\n"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
function parseArgs(argv) {
|
|
178
|
+
const parsed = {};
|
|
179
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
180
|
+
const entry = argv[index];
|
|
181
|
+
if (!entry.startsWith("--")) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const next = argv[index + 1];
|
|
185
|
+
if (next && !next.startsWith("--")) {
|
|
186
|
+
parsed[entry.slice(2)] = next;
|
|
187
|
+
index += 1;
|
|
188
|
+
} else {
|
|
189
|
+
parsed[entry.slice(2)] = "true";
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return parsed;
|
|
193
|
+
}
|
|
194
|
+
function parseInstallTarget(target) {
|
|
195
|
+
if (target === "qclaw" || target === "openclaw") {
|
|
196
|
+
return target;
|
|
197
|
+
}
|
|
198
|
+
return void 0;
|
|
199
|
+
}
|
|
200
|
+
async function copyPublishablePackage(packageRoot, destination) {
|
|
201
|
+
if (!(0, import_node_fs.existsSync)(packageRoot)) {
|
|
202
|
+
throw new Error(`package root does not exist: ${packageRoot}`);
|
|
203
|
+
}
|
|
204
|
+
for (const relativePath of COPY_ITEMS) {
|
|
205
|
+
const source = (0, import_node_path.join)(packageRoot, relativePath);
|
|
206
|
+
if (!(0, import_node_fs.existsSync)(source)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const target = (0, import_node_path.join)(destination, relativePath);
|
|
210
|
+
await (0, import_promises.rm)(target, { recursive: true, force: true });
|
|
211
|
+
await (0, import_promises.cp)(source, target, { recursive: true, force: true });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function readOpenClawConfig(configPath) {
|
|
215
|
+
if (!(0, import_node_fs.existsSync)(configPath)) {
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
return JSON.parse(await (0, import_promises.readFile)(configPath, "utf8"));
|
|
219
|
+
}
|
|
220
|
+
function inferGatewaySettings(config) {
|
|
221
|
+
const gateway = config.gateway ?? {};
|
|
222
|
+
const host = typeof gateway.host === "string" && gateway.host.trim() ? gateway.host.trim() : "127.0.0.1";
|
|
223
|
+
const port = Number(gateway.port ?? 28789);
|
|
224
|
+
const baseUrl = Number.isFinite(port) && port > 0 ? `ws://${host}:${port}` : "";
|
|
225
|
+
const auth = gateway.auth ?? {};
|
|
226
|
+
const mode = typeof auth.mode === "string" ? auth.mode : "token";
|
|
227
|
+
const secret = mode === "password" ? typeof auth.password === "string" ? auth.password : "" : typeof auth.token === "string" ? auth.token : "";
|
|
228
|
+
return { baseUrl, secret };
|
|
229
|
+
}
|
|
230
|
+
function inferHub53AISettings(config) {
|
|
231
|
+
const legacy = config.channels?.["53aihub"];
|
|
232
|
+
if (!legacy) {
|
|
233
|
+
return {
|
|
234
|
+
enabled: false,
|
|
235
|
+
botId: "",
|
|
236
|
+
secret: "",
|
|
237
|
+
wsUrl: ""
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
enabled: legacy.enabled !== false,
|
|
242
|
+
botId: typeof legacy.botId === "string" ? legacy.botId : "",
|
|
243
|
+
secret: typeof legacy.secret === "string" ? legacy.secret : typeof legacy.token === "string" ? legacy.token : "",
|
|
244
|
+
wsUrl: typeof legacy.WSUrl === "string" ? legacy.WSUrl : typeof legacy.websocketUrl === "string" ? legacy.websocketUrl : "",
|
|
245
|
+
accessPolicy: typeof legacy.accessPolicy === "string" ? legacy.accessPolicy : void 0,
|
|
246
|
+
allowFrom: Array.isArray(legacy.allowFrom) ? legacy.allowFrom.map((entry) => String(entry)).filter(Boolean) : void 0,
|
|
247
|
+
sendThinkingMessage: typeof legacy.sendThinkingMessage === "boolean" ? legacy.sendThinkingMessage : void 0
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function parseOptionalBoolean(value) {
|
|
251
|
+
if (value === void 0) {
|
|
252
|
+
return void 0;
|
|
253
|
+
}
|
|
254
|
+
if (value === "" || value === "true" || value === "1" || value === "yes") {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (value === "false" || value === "0" || value === "no") {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
throw new Error(`invalid boolean value: ${value}`);
|
|
261
|
+
}
|
|
262
|
+
function normalizePort(port) {
|
|
263
|
+
if (port === void 0) {
|
|
264
|
+
return void 0;
|
|
265
|
+
}
|
|
266
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
267
|
+
throw new Error("console port must be a positive number");
|
|
268
|
+
}
|
|
269
|
+
return Math.floor(port);
|
|
270
|
+
}
|
|
271
|
+
function ensureObject(record, key) {
|
|
272
|
+
const existing = record[key];
|
|
273
|
+
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
274
|
+
return existing;
|
|
275
|
+
}
|
|
276
|
+
const created = {};
|
|
277
|
+
record[key] = created;
|
|
278
|
+
return created;
|
|
279
|
+
}
|
|
280
|
+
function dedupeStrings(values) {
|
|
281
|
+
return Array.from(new Set(values.filter((value) => typeof value === "string")));
|
|
282
|
+
}
|
|
283
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
284
|
+
0 && (module.exports = {
|
|
285
|
+
installIntoOpenClaw,
|
|
286
|
+
installIntoQClaw,
|
|
287
|
+
runInstallCommand
|
|
288
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
type InstallInput = {
|
|
2
|
+
packageRoot: string;
|
|
3
|
+
extensionsDir: string;
|
|
4
|
+
configPath: string;
|
|
5
|
+
gateway?: string;
|
|
6
|
+
botId?: string;
|
|
7
|
+
secret?: string;
|
|
8
|
+
hubWsUrl?: string;
|
|
9
|
+
hubBotId?: string;
|
|
10
|
+
hubSecret?: string;
|
|
11
|
+
hubEnabled?: boolean;
|
|
12
|
+
consoleHost?: string;
|
|
13
|
+
consolePort?: number;
|
|
14
|
+
};
|
|
15
|
+
declare function installIntoQClaw(input: InstallInput): Promise<{
|
|
16
|
+
configPath: string;
|
|
17
|
+
extensionsDir: string;
|
|
18
|
+
destination: string;
|
|
19
|
+
gatewayBaseUrl: string;
|
|
20
|
+
hub53aiConfigured: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
declare function installIntoOpenClaw(input: InstallInput): Promise<{
|
|
23
|
+
configPath: string;
|
|
24
|
+
extensionsDir: string;
|
|
25
|
+
destination: string;
|
|
26
|
+
gatewayBaseUrl: string;
|
|
27
|
+
hub53aiConfigured: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
declare function runInstallCommand(input: {
|
|
30
|
+
argv?: string[];
|
|
31
|
+
packageRoot: string;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
|
|
34
|
+
export { installIntoOpenClaw, installIntoQClaw, runInstallCommand };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "claw-control-center",
|
|
3
|
+
"name": "Claw Control Center",
|
|
4
|
+
"description": "QClaw/OpenClaw multi-session control plugin with a local web console.",
|
|
5
|
+
"activation": {
|
|
6
|
+
"onStartup": true
|
|
7
|
+
},
|
|
8
|
+
"configSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": {
|
|
12
|
+
"gateway": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"properties": {
|
|
16
|
+
"baseUrl": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Custom gateway base URL, for example https://gateway.example.com/v1"
|
|
19
|
+
},
|
|
20
|
+
"botId": {
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
"secret": {
|
|
24
|
+
"type": "string"
|
|
25
|
+
},
|
|
26
|
+
"requestTimeoutMs": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"default": 15000
|
|
29
|
+
},
|
|
30
|
+
"streamReconnectMs": {
|
|
31
|
+
"type": "number",
|
|
32
|
+
"default": 2000
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"hub53ai": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"properties": {
|
|
40
|
+
"enabled": {
|
|
41
|
+
"type": "boolean",
|
|
42
|
+
"default": false
|
|
43
|
+
},
|
|
44
|
+
"botId": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "53AIHub Bot ID."
|
|
47
|
+
},
|
|
48
|
+
"secret": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "53AIHub App Secret / Token."
|
|
51
|
+
},
|
|
52
|
+
"wsUrl": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "53AIHub WebSocket URL, for example wss://example.com/api/v1/openclaw/ws/connect"
|
|
55
|
+
},
|
|
56
|
+
"accessPolicy": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"enum": [
|
|
59
|
+
"open",
|
|
60
|
+
"allowlist"
|
|
61
|
+
],
|
|
62
|
+
"default": "open"
|
|
63
|
+
},
|
|
64
|
+
"allowFrom": {
|
|
65
|
+
"type": "array",
|
|
66
|
+
"items": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"sendThinkingMessage": {
|
|
71
|
+
"type": "boolean",
|
|
72
|
+
"default": true
|
|
73
|
+
},
|
|
74
|
+
"reconnectBaseMs": {
|
|
75
|
+
"type": "number",
|
|
76
|
+
"default": 2000
|
|
77
|
+
},
|
|
78
|
+
"maxReconnectAttempts": {
|
|
79
|
+
"type": "number",
|
|
80
|
+
"default": 10
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"console": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"additionalProperties": false,
|
|
87
|
+
"properties": {
|
|
88
|
+
"enabled": {
|
|
89
|
+
"type": "boolean",
|
|
90
|
+
"default": true
|
|
91
|
+
},
|
|
92
|
+
"host": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"default": "127.0.0.1"
|
|
95
|
+
},
|
|
96
|
+
"port": {
|
|
97
|
+
"type": "number",
|
|
98
|
+
"default": 4318
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"persistence": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"properties": {
|
|
106
|
+
"maxSessions": {
|
|
107
|
+
"type": "number",
|
|
108
|
+
"default": 100
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"logging": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"additionalProperties": false,
|
|
115
|
+
"properties": {
|
|
116
|
+
"level": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"enum": [
|
|
119
|
+
"debug",
|
|
120
|
+
"info",
|
|
121
|
+
"warn",
|
|
122
|
+
"error"
|
|
123
|
+
],
|
|
124
|
+
"default": "info"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claw-control-center",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"claw-control-center": "bin/install-qclaw.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"openclaw.plugin.json",
|
|
13
|
+
"bin",
|
|
14
|
+
"web-dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "node ./scripts/sync-web-dist.mjs && tsup src/index.ts src/install-qclaw.ts --format cjs --dts",
|
|
18
|
+
"test": "vitest run"
|
|
19
|
+
},
|
|
20
|
+
"openclaw": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./dist/index.cjs"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"openclaw": ">=2026.5.18"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"ws": "^8.18.3"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^24.10.1",
|
|
33
|
+
"@types/ws": "^8.18.1",
|
|
34
|
+
"openclaw": "2026.5.18",
|
|
35
|
+
"tsup": "^8.5.1",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vitest": "^3.2.4"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
html,body,#root{height:100%}:root{font-family:SF Pro Display,Segoe UI,sans-serif;color:#e6eef7;background:radial-gradient(circle at top left,rgba(92,148,255,.2),transparent 32%),linear-gradient(160deg,#0b1220,#121b2d 48%,#0c1424)}*{box-sizing:border-box}body{margin:0;min-height:100vh;background:transparent}button,input,textarea{font:inherit}.app-shell{display:grid;grid-template-columns:280px minmax(0,1fr) 340px;gap:16px;height:100vh;min-height:100vh;padding:16px;align-items:stretch}.panel{border:1px solid rgba(148,163,184,.18);background:#090f1cb8;border-radius:20px;box-shadow:0 18px 48px #00000047;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.panel-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px 18px 14px;border-bottom:1px solid rgba(148,163,184,.14)}.panel-header h2,.panel-header h3{margin:0;font-size:1rem;font-weight:700}.panel-subtitle{margin:4px 0 0;font-size:.8rem;color:#8ea4bc}.sessions-panel,.sidebar-panel,.conversation-panel{overflow:hidden;min-height:0}.sessions-panel{display:grid;grid-template-rows:auto minmax(0,1fr)}.session-list{display:flex;flex-direction:column;gap:10px;padding:14px;min-height:0;overflow-y:auto}.session-item{width:100%;padding:14px;border:1px solid rgba(148,163,184,.14);border-radius:16px;background:#0f172ac7;color:inherit;text-align:left;cursor:pointer}.session-item.is-active{border-color:#60a5fa80;background:#1c2e52e0}.session-item.session-running{border-color:#2dd4bf5c;box-shadow:inset 0 0 0 1px #2dd4bf2e}.session-title{display:block;font-weight:700;margin-bottom:8px;line-height:1.35;word-break:break-word}.session-meta{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:.8rem;color:#9fb3c8}.status-pill{display:inline-flex;align-items:center;padding:4px 10px;border-radius:999px;font-size:.75rem;text-transform:capitalize;background:#3b82f629;color:#bfdbfe}.status-running{background:#10b9812e;color:#b7f7de}.status-completed{background:#3b82f629;color:#bfdbfe}.status-idle{background:#94a3b829;color:#dbe7f5}.status-failed,.status-interrupted{background:#f8717129;color:#fecaca}.session-updated{display:block;margin-top:8px;font-size:.75rem;color:#7e94ad}.conversation-panel{display:grid;grid-template-rows:auto minmax(0,1fr) auto}.conversation-body{display:flex;flex-direction:column;gap:14px;padding:18px;min-height:0;overflow-y:auto}.message{max-width:80%;padding:14px 16px;border-radius:18px;line-height:1.45;white-space:pre-wrap}.message.user{align-self:flex-end;background:linear-gradient(135deg,#2563eb,#1d4ed8)}.message.assistant{align-self:flex-start;background:#1e293beb;border:1px solid rgba(148,163,184,.14)}.message small{display:block;margin-top:8px;color:#9fb3c8}.activity-card{align-self:stretch;display:flex;flex-direction:column;gap:12px;min-height:96px;border-radius:18px;border:1px solid rgba(148,163,184,.14);background:#0f172ac2;overflow:visible;padding:14px 16px}.activity-header{display:flex;flex-direction:column;align-items:stretch;gap:10px}.activity-summary{display:flex;flex-direction:column;gap:4px;min-width:0}.activity-summary strong,.activity-summary span{display:block;overflow-wrap:anywhere}.activity-summary strong{font-size:.92rem;line-height:1.35}.activity-meta{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px}.activity-summary span,.activity-card time,.activity-details p{color:#b8cadc;font-size:.82rem;line-height:1.45}.activity-card time{flex:0 1 auto;white-space:nowrap}.activity-details-toggle{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:6px 12px;border:1px solid rgba(96,165,250,.24);border-radius:10px;background:#2563eb24;color:#9dc4ff;font:inherit;font-size:.82rem;font-weight:600;line-height:1.2;cursor:pointer}.activity-details-toggle:hover{color:#bfdbfe}.activity-details-toggle:focus-visible{outline:2px solid rgba(96,165,250,.7);outline-offset:3px;border-radius:6px}.activity-details{display:grid;gap:8px;width:100%;min-height:52px;padding:10px 12px 12px;border-top:1px solid rgba(148,163,184,.12);border-radius:12px;background:#070c188c}.activity-details p{margin:0;color:#d7e3f2}.tone-success{border-color:#3b82f638}.tone-warning{border-color:#f8717142}.composer{padding:16px;border-top:1px solid rgba(148,163,184,.14)}.composer form{display:grid;gap:10px}.composer textarea{width:100%;min-height:112px;resize:vertical;border-radius:16px;border:1px solid rgba(148,163,184,.18);background:#080c18f2;color:inherit;padding:14px}.toolbar-row{display:flex;justify-content:space-between;gap:10px}.primary-button,.secondary-button{border:0;border-radius:14px;padding:10px 14px;cursor:pointer}.primary-button{color:#fff;background:linear-gradient(135deg,#2563eb,#0f766e)}.secondary-button{color:#dce9f9;background:#1e293be0;border:1px solid rgba(148,163,184,.16)}.sidebar-scroll{display:grid;gap:14px;padding:14px;min-height:0;overflow-y:auto}.stat-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.stat-card-wide{grid-column:1 / -1}.stat-card,.timeline-card,.config-card{padding:14px;border-radius:16px;background:#0f172ac2;border:1px solid rgba(148,163,184,.14)}.stat-label{display:block;font-size:.78rem;color:#9fb3c8;margin-bottom:6px}.stat-value{font-size:1.15rem;font-weight:700}.stat-value-compact{font-size:.92rem;line-height:1.45;word-break:break-word}.config-card details{margin-top:10px}.config-card summary{cursor:pointer;color:#9dc4ff;font-size:.8rem}.config-card pre{margin:0;white-space:pre-wrap;word-break:break-word;font-size:.78rem;color:#c8d5e6}.empty-state,.loading-state,.error-banner{margin:18px;padding:14px 16px;border-radius:16px;background:#1e293bc2;color:#d8e7f5}.error-banner{background:#7f1d1d80;border:1px solid rgba(252,165,165,.3)}@media(max-width:1200px){.app-shell{grid-template-columns:260px minmax(0,1fr);height:auto;min-height:100vh;align-items:start;overflow-y:auto}.sessions-panel,.conversation-panel{min-height:calc(100vh - 32px)}.sidebar-panel{grid-column:1 / -1;min-height:360px}.activity-header{flex-direction:column;align-items:stretch;gap:8px}.activity-meta{align-items:center;justify-content:space-between;width:100%}.toolbar-row{flex-direction:column;align-items:stretch}}@media(max-width:820px){.app-shell{grid-template-columns:1fr}.sessions-panel,.conversation-panel,.sidebar-panel{min-height:auto}.message{max-width:100%}}
|