nextclaw 0.4.10 → 0.4.12
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/cli/index.js +220 -6
- package/package.json +4 -4
package/dist/cli/index.js
CHANGED
|
@@ -42,7 +42,12 @@ import {
|
|
|
42
42
|
installPluginFromNpmSpec,
|
|
43
43
|
uninstallPlugin,
|
|
44
44
|
resolveUninstallDirectoryTarget,
|
|
45
|
-
|
|
45
|
+
setPluginRuntimeBridge,
|
|
46
|
+
getPluginChannelBindings,
|
|
47
|
+
getPluginUiMetadataFromRegistry,
|
|
48
|
+
resolvePluginChannelMessageToolHints,
|
|
49
|
+
startPluginChannelGateways,
|
|
50
|
+
stopPluginChannelGateways
|
|
46
51
|
} from "@nextclaw/openclaw-compat";
|
|
47
52
|
import { startUiServer } from "@nextclaw/server";
|
|
48
53
|
import {
|
|
@@ -78,7 +83,7 @@ import { join, resolve } from "path";
|
|
|
78
83
|
import { spawn } from "child_process";
|
|
79
84
|
import { createServer } from "net";
|
|
80
85
|
import { fileURLToPath } from "url";
|
|
81
|
-
import { getDataDir, getPackageVersion } from "@nextclaw/core";
|
|
86
|
+
import { getDataDir, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
|
|
82
87
|
function resolveUiConfig(config, overrides) {
|
|
83
88
|
const base = config.ui ?? { enabled: false, host: "127.0.0.1", port: 18791, open: false };
|
|
84
89
|
return { ...base, ...overrides ?? {} };
|
|
@@ -229,6 +234,34 @@ function which(binary) {
|
|
|
229
234
|
}
|
|
230
235
|
return false;
|
|
231
236
|
}
|
|
237
|
+
function resolveVersionFromPackageTree(startDir, expectedName) {
|
|
238
|
+
let current = resolve(startDir);
|
|
239
|
+
while (current.length > 0) {
|
|
240
|
+
const pkgPath = join(current, "package.json");
|
|
241
|
+
if (existsSync(pkgPath)) {
|
|
242
|
+
try {
|
|
243
|
+
const raw = readFileSync(pkgPath, "utf-8");
|
|
244
|
+
const parsed = JSON.parse(raw);
|
|
245
|
+
if (typeof parsed.version === "string") {
|
|
246
|
+
if (!expectedName || parsed.name === expectedName) {
|
|
247
|
+
return parsed.version;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const parent = resolve(current, "..");
|
|
254
|
+
if (parent === current) {
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
current = parent;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
function getPackageVersion() {
|
|
262
|
+
const cliDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
|
|
263
|
+
return resolveVersionFromPackageTree(cliDir, "nextclaw") ?? resolveVersionFromPackageTree(cliDir) ?? getCorePackageVersion();
|
|
264
|
+
}
|
|
232
265
|
function startUiFrontend(options) {
|
|
233
266
|
const uiDir = options.dir ?? resolveUiFrontendDir();
|
|
234
267
|
if (!uiDir) {
|
|
@@ -888,7 +921,13 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
888
921
|
restrictToWorkspace: config.tools.restrictToWorkspace,
|
|
889
922
|
contextConfig: config.agents.context,
|
|
890
923
|
config,
|
|
891
|
-
extensionRegistry
|
|
924
|
+
extensionRegistry,
|
|
925
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
926
|
+
registry: pluginRegistry,
|
|
927
|
+
channel,
|
|
928
|
+
cfg: loadConfig(),
|
|
929
|
+
accountId
|
|
930
|
+
})
|
|
892
931
|
});
|
|
893
932
|
if (opts.message) {
|
|
894
933
|
const response = await agentLoop.processDirect({
|
|
@@ -1347,6 +1386,21 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1347
1386
|
console.log(`Telegram: ${config.channels.telegram.enabled ? "\u2713" : "\u2717"}`);
|
|
1348
1387
|
console.log(`Slack: ${config.channels.slack.enabled ? "\u2713" : "\u2717"}`);
|
|
1349
1388
|
console.log(`QQ: ${config.channels.qq.enabled ? "\u2713" : "\u2717"}`);
|
|
1389
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1390
|
+
const report = buildPluginStatusReport({
|
|
1391
|
+
config,
|
|
1392
|
+
workspaceDir,
|
|
1393
|
+
reservedChannelIds: Object.keys(config.channels),
|
|
1394
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1395
|
+
});
|
|
1396
|
+
const pluginChannels = report.plugins.filter((plugin) => plugin.status === "loaded" && plugin.channelIds.length > 0);
|
|
1397
|
+
if (pluginChannels.length > 0) {
|
|
1398
|
+
console.log("Plugin Channels:");
|
|
1399
|
+
for (const plugin of pluginChannels) {
|
|
1400
|
+
const channels2 = plugin.channelIds.join(", ");
|
|
1401
|
+
console.log(`- ${channels2} (plugin: ${plugin.id})`);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1350
1404
|
}
|
|
1351
1405
|
channelsLogin() {
|
|
1352
1406
|
const bridgeDir = this.getBridgeDir();
|
|
@@ -1357,6 +1411,95 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1357
1411
|
console.error(`Bridge failed: ${result.status ?? 1}`);
|
|
1358
1412
|
}
|
|
1359
1413
|
}
|
|
1414
|
+
channelsAdd(opts) {
|
|
1415
|
+
const channelId = opts.channel?.trim();
|
|
1416
|
+
if (!channelId) {
|
|
1417
|
+
console.error("--channel is required");
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
const config = loadConfig();
|
|
1421
|
+
const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
|
|
1422
|
+
const pluginRegistry = this.loadPluginRegistry(config, workspaceDir);
|
|
1423
|
+
const bindings = getPluginChannelBindings(pluginRegistry);
|
|
1424
|
+
const binding = bindings.find((entry) => entry.channelId === channelId || entry.pluginId === channelId);
|
|
1425
|
+
if (!binding) {
|
|
1426
|
+
console.error(`No plugin channel found for: ${channelId}`);
|
|
1427
|
+
process.exit(1);
|
|
1428
|
+
}
|
|
1429
|
+
const setup = binding.channel.setup;
|
|
1430
|
+
if (!setup?.applyAccountConfig) {
|
|
1431
|
+
console.error(`Channel "${binding.channelId}" does not support setup.`);
|
|
1432
|
+
process.exit(1);
|
|
1433
|
+
}
|
|
1434
|
+
const input = {
|
|
1435
|
+
name: opts.name,
|
|
1436
|
+
token: opts.token,
|
|
1437
|
+
code: opts.code,
|
|
1438
|
+
url: opts.url,
|
|
1439
|
+
httpUrl: opts.httpUrl
|
|
1440
|
+
};
|
|
1441
|
+
const currentView = this.toPluginConfigView(config, bindings);
|
|
1442
|
+
const accountId = binding.channel.config?.defaultAccountId?.(currentView) ?? "default";
|
|
1443
|
+
const validateError = setup.validateInput?.({
|
|
1444
|
+
cfg: currentView,
|
|
1445
|
+
input,
|
|
1446
|
+
accountId
|
|
1447
|
+
});
|
|
1448
|
+
if (validateError) {
|
|
1449
|
+
console.error(`Channel setup validation failed: ${validateError}`);
|
|
1450
|
+
process.exit(1);
|
|
1451
|
+
}
|
|
1452
|
+
const nextView = setup.applyAccountConfig({
|
|
1453
|
+
cfg: currentView,
|
|
1454
|
+
input,
|
|
1455
|
+
accountId
|
|
1456
|
+
});
|
|
1457
|
+
if (!nextView || typeof nextView !== "object" || Array.isArray(nextView)) {
|
|
1458
|
+
console.error("Channel setup returned invalid config payload.");
|
|
1459
|
+
process.exit(1);
|
|
1460
|
+
}
|
|
1461
|
+
let next = this.mergePluginConfigView(config, nextView, bindings);
|
|
1462
|
+
next = enablePluginInConfig(next, binding.pluginId);
|
|
1463
|
+
saveConfig(next);
|
|
1464
|
+
console.log(`Configured channel "${binding.channelId}" via plugin "${binding.pluginId}".`);
|
|
1465
|
+
console.log("Restart the gateway to apply changes.");
|
|
1466
|
+
}
|
|
1467
|
+
toPluginConfigView(config, bindings) {
|
|
1468
|
+
const view = JSON.parse(JSON.stringify(config));
|
|
1469
|
+
const channels2 = view.channels && typeof view.channels === "object" && !Array.isArray(view.channels) ? { ...view.channels } : {};
|
|
1470
|
+
for (const binding of bindings) {
|
|
1471
|
+
const pluginConfig = config.plugins.entries?.[binding.pluginId]?.config;
|
|
1472
|
+
if (!pluginConfig || typeof pluginConfig !== "object" || Array.isArray(pluginConfig)) {
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
channels2[binding.channelId] = JSON.parse(JSON.stringify(pluginConfig));
|
|
1476
|
+
}
|
|
1477
|
+
view.channels = channels2;
|
|
1478
|
+
return view;
|
|
1479
|
+
}
|
|
1480
|
+
mergePluginConfigView(baseConfig, pluginViewConfig, bindings) {
|
|
1481
|
+
const next = JSON.parse(JSON.stringify(baseConfig));
|
|
1482
|
+
const pluginChannels = pluginViewConfig.channels && typeof pluginViewConfig.channels === "object" && !Array.isArray(pluginViewConfig.channels) ? pluginViewConfig.channels : {};
|
|
1483
|
+
const entries = { ...next.plugins.entries ?? {} };
|
|
1484
|
+
for (const binding of bindings) {
|
|
1485
|
+
if (!Object.prototype.hasOwnProperty.call(pluginChannels, binding.channelId)) {
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
const channelConfig = pluginChannels[binding.channelId];
|
|
1489
|
+
if (!channelConfig || typeof channelConfig !== "object" || Array.isArray(channelConfig)) {
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
entries[binding.pluginId] = {
|
|
1493
|
+
...entries[binding.pluginId] ?? {},
|
|
1494
|
+
config: channelConfig
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
next.plugins = {
|
|
1498
|
+
...next.plugins,
|
|
1499
|
+
entries
|
|
1500
|
+
};
|
|
1501
|
+
return next;
|
|
1502
|
+
}
|
|
1360
1503
|
cronList(opts) {
|
|
1361
1504
|
const storePath = join3(getDataDir2(), "cron", "jobs.json");
|
|
1362
1505
|
const service = new CronService(storePath);
|
|
@@ -1526,7 +1669,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1526
1669
|
const sessionManager = new SessionManager(workspace);
|
|
1527
1670
|
const cronStorePath = join3(getDataDir2(), "cron", "jobs.json");
|
|
1528
1671
|
const cron2 = new CronService(cronStorePath);
|
|
1529
|
-
const pluginUiMetadata =
|
|
1672
|
+
const pluginUiMetadata = getPluginUiMetadataFromRegistry(pluginRegistry);
|
|
1530
1673
|
const uiConfig = resolveUiConfig(config, options.uiOverrides);
|
|
1531
1674
|
const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
|
|
1532
1675
|
if (!provider) {
|
|
@@ -1571,7 +1714,52 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1571
1714
|
contextConfig: config.agents.context,
|
|
1572
1715
|
gatewayController,
|
|
1573
1716
|
config,
|
|
1574
|
-
extensionRegistry
|
|
1717
|
+
extensionRegistry,
|
|
1718
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
1719
|
+
registry: pluginRegistry,
|
|
1720
|
+
channel,
|
|
1721
|
+
cfg: loadConfig(),
|
|
1722
|
+
accountId
|
|
1723
|
+
})
|
|
1724
|
+
});
|
|
1725
|
+
const pluginChannelBindings = getPluginChannelBindings(pluginRegistry);
|
|
1726
|
+
setPluginRuntimeBridge({
|
|
1727
|
+
loadConfig: () => this.toPluginConfigView(loadConfig(), pluginChannelBindings),
|
|
1728
|
+
writeConfigFile: async (nextConfigView) => {
|
|
1729
|
+
if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
|
|
1730
|
+
throw new Error("plugin runtime writeConfigFile expects an object config");
|
|
1731
|
+
}
|
|
1732
|
+
const current = loadConfig();
|
|
1733
|
+
const next = this.mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
|
|
1734
|
+
saveConfig(next);
|
|
1735
|
+
},
|
|
1736
|
+
dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
|
|
1737
|
+
const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
|
|
1738
|
+
const body = typeof ctx.Body === "string" ? ctx.Body : "";
|
|
1739
|
+
const content = (bodyForAgent || body).trim();
|
|
1740
|
+
if (!content) {
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
const sessionKey = typeof ctx.SessionKey === "string" && ctx.SessionKey.trim().length > 0 ? ctx.SessionKey : `plugin:${typeof ctx.OriginatingChannel === "string" ? ctx.OriginatingChannel : "channel"}:${typeof ctx.SenderId === "string" ? ctx.SenderId : "unknown"}`;
|
|
1744
|
+
const channel = typeof ctx.OriginatingChannel === "string" && ctx.OriginatingChannel.trim().length > 0 ? ctx.OriginatingChannel : "cli";
|
|
1745
|
+
const chatId = typeof ctx.OriginatingTo === "string" && ctx.OriginatingTo.trim().length > 0 ? ctx.OriginatingTo : typeof ctx.SenderId === "string" && ctx.SenderId.trim().length > 0 ? ctx.SenderId : "direct";
|
|
1746
|
+
try {
|
|
1747
|
+
const response = await agent.processDirect({
|
|
1748
|
+
content,
|
|
1749
|
+
sessionKey,
|
|
1750
|
+
channel,
|
|
1751
|
+
chatId,
|
|
1752
|
+
metadata: typeof ctx.AccountId === "string" && ctx.AccountId.trim().length > 0 ? { account_id: ctx.AccountId } : {}
|
|
1753
|
+
});
|
|
1754
|
+
const replyText = typeof response === "string" ? response : String(response ?? "");
|
|
1755
|
+
if (replyText.trim()) {
|
|
1756
|
+
await dispatcherOptions.deliver({ text: replyText }, { kind: "final" });
|
|
1757
|
+
}
|
|
1758
|
+
} catch (error) {
|
|
1759
|
+
dispatcherOptions.onError?.(error);
|
|
1760
|
+
throw error;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1575
1763
|
});
|
|
1576
1764
|
cron2.onJob = async (job) => {
|
|
1577
1765
|
const response = await agent.processDirect({
|
|
@@ -1618,7 +1806,32 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1618
1806
|
watcher.on("unlink", () => reloader.scheduleReload("config unlink"));
|
|
1619
1807
|
await cron2.start();
|
|
1620
1808
|
await heartbeat.start();
|
|
1621
|
-
|
|
1809
|
+
let pluginGatewayHandles = [];
|
|
1810
|
+
try {
|
|
1811
|
+
const startedPluginGateways = await startPluginChannelGateways({
|
|
1812
|
+
registry: pluginRegistry,
|
|
1813
|
+
logger: {
|
|
1814
|
+
info: (message) => console.log(`[plugins] ${message}`),
|
|
1815
|
+
warn: (message) => console.warn(`[plugins] ${message}`),
|
|
1816
|
+
error: (message) => console.error(`[plugins] ${message}`),
|
|
1817
|
+
debug: (message) => console.debug(`[plugins] ${message}`)
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
pluginGatewayHandles = startedPluginGateways.handles;
|
|
1821
|
+
for (const diag of startedPluginGateways.diagnostics) {
|
|
1822
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
1823
|
+
const text = `${prefix}${diag.message}`;
|
|
1824
|
+
if (diag.level === "error") {
|
|
1825
|
+
console.error(`[plugins] ${text}`);
|
|
1826
|
+
} else {
|
|
1827
|
+
console.warn(`[plugins] ${text}`);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
await Promise.allSettled([agent.run(), reloader.getChannels().startAll()]);
|
|
1831
|
+
} finally {
|
|
1832
|
+
await stopPluginChannelGateways(pluginGatewayHandles);
|
|
1833
|
+
setPluginRuntimeBridge(null);
|
|
1834
|
+
}
|
|
1622
1835
|
}
|
|
1623
1836
|
startUiIfEnabled(uiConfig, uiStaticDir) {
|
|
1624
1837
|
if (!uiConfig.enabled) {
|
|
@@ -2019,6 +2232,7 @@ plugins.command("uninstall <id>").description("Uninstall a plugin").option("--ke
|
|
|
2019
2232
|
plugins.command("install <path-or-spec>").description("Install a plugin (path, archive, or npm spec)").option("-l, --link", "Link a local path instead of copying", false).action(async (pathOrSpec, opts) => runtime.pluginsInstall(pathOrSpec, opts));
|
|
2020
2233
|
plugins.command("doctor").description("Report plugin load issues").action(() => runtime.pluginsDoctor());
|
|
2021
2234
|
var channels = program.command("channels").description("Manage channels");
|
|
2235
|
+
channels.command("add").description("Configure a plugin channel (OpenClaw-compatible setup)").requiredOption("--channel <id>", "Plugin channel id").option("--code <code>", "Pairing code").option("--token <token>", "Connector token").option("--name <name>", "Display name").option("--url <url>", "API base URL").option("--http-url <url>", "Alias for --url").action((opts) => runtime.channelsAdd(opts));
|
|
2022
2236
|
channels.command("status").description("Show channel status").action(() => runtime.channelsStatus());
|
|
2023
2237
|
channels.command("login").description("Link device via QR code").action(() => runtime.channelsLogin());
|
|
2024
2238
|
var cron = program.command("cron").description("Manage scheduled tasks");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextclaw",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
4
4
|
"description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"chokidar": "^3.6.0",
|
|
40
40
|
"commander": "^12.1.0",
|
|
41
|
-
"@nextclaw/core": "^0.4.
|
|
42
|
-
"@nextclaw/server": "^0.3.
|
|
43
|
-
"@nextclaw/openclaw-compat": "^0.1.
|
|
41
|
+
"@nextclaw/core": "^0.4.10",
|
|
42
|
+
"@nextclaw/server": "^0.3.5",
|
|
43
|
+
"@nextclaw/openclaw-compat": "^0.1.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^20.17.6",
|