nextclaw 0.4.12 → 0.4.13
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 +364 -102
- package/package.json +3 -3
package/dist/cli/index.js
CHANGED
|
@@ -84,8 +84,8 @@ import { spawn } from "child_process";
|
|
|
84
84
|
import { createServer } from "net";
|
|
85
85
|
import { fileURLToPath } from "url";
|
|
86
86
|
import { getDataDir, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
|
|
87
|
-
function resolveUiConfig(
|
|
88
|
-
const base =
|
|
87
|
+
function resolveUiConfig(config2, overrides) {
|
|
88
|
+
const base = config2.ui ?? { enabled: false, host: "127.0.0.1", port: 18791, open: false };
|
|
89
89
|
return { ...base, ...overrides ?? {} };
|
|
90
90
|
}
|
|
91
91
|
function resolveUiApiBase(host, port) {
|
|
@@ -389,21 +389,21 @@ var readConfigSnapshot = (getConfigPath2, plugins2) => {
|
|
|
389
389
|
parsed = {};
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
|
-
let
|
|
392
|
+
let config2;
|
|
393
393
|
let valid = true;
|
|
394
394
|
try {
|
|
395
|
-
|
|
395
|
+
config2 = ConfigSchema.parse(parsed);
|
|
396
396
|
} catch {
|
|
397
|
-
|
|
397
|
+
config2 = ConfigSchema.parse({});
|
|
398
398
|
valid = false;
|
|
399
399
|
}
|
|
400
400
|
if (!raw) {
|
|
401
|
-
raw = JSON.stringify(
|
|
401
|
+
raw = JSON.stringify(config2, null, 2);
|
|
402
402
|
}
|
|
403
403
|
const hash = hashRaw(raw);
|
|
404
404
|
const schema = buildConfigSchema({ version: getPackageVersion(), plugins: plugins2 });
|
|
405
|
-
const redacted = redactConfigObject(
|
|
406
|
-
return { raw: valid ? JSON.stringify(redacted, null, 2) : null, hash: valid ? hash : null, config, redacted, valid };
|
|
405
|
+
const redacted = redactConfigObject(config2, schema.uiHints);
|
|
406
|
+
return { raw: valid ? JSON.stringify(redacted, null, 2) : null, hash: valid ? hash : null, config: config2, redacted, valid };
|
|
407
407
|
};
|
|
408
408
|
var redactValue = (value, plugins2) => {
|
|
409
409
|
const schema = buildConfigSchema({ version: getPackageVersion(), plugins: plugins2 });
|
|
@@ -627,6 +627,191 @@ function buildClawHubArgs(slug, options) {
|
|
|
627
627
|
// src/cli/runtime.ts
|
|
628
628
|
var LOGO = "\u{1F916}";
|
|
629
629
|
var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "/exit", "/quit", ":q"]);
|
|
630
|
+
function isIndexSegment(raw) {
|
|
631
|
+
return /^[0-9]+$/.test(raw);
|
|
632
|
+
}
|
|
633
|
+
function parseConfigPath(raw) {
|
|
634
|
+
const trimmed = raw.trim();
|
|
635
|
+
if (!trimmed) {
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
const parts = [];
|
|
639
|
+
let current = "";
|
|
640
|
+
let i = 0;
|
|
641
|
+
while (i < trimmed.length) {
|
|
642
|
+
const ch = trimmed[i];
|
|
643
|
+
if (ch === "\\") {
|
|
644
|
+
const next = trimmed[i + 1];
|
|
645
|
+
if (next) {
|
|
646
|
+
current += next;
|
|
647
|
+
}
|
|
648
|
+
i += 2;
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (ch === ".") {
|
|
652
|
+
if (current) {
|
|
653
|
+
parts.push(current);
|
|
654
|
+
}
|
|
655
|
+
current = "";
|
|
656
|
+
i += 1;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (ch === "[") {
|
|
660
|
+
if (current) {
|
|
661
|
+
parts.push(current);
|
|
662
|
+
}
|
|
663
|
+
current = "";
|
|
664
|
+
const close = trimmed.indexOf("]", i);
|
|
665
|
+
if (close === -1) {
|
|
666
|
+
throw new Error(`Invalid path (missing "]"): ${raw}`);
|
|
667
|
+
}
|
|
668
|
+
const inside = trimmed.slice(i + 1, close).trim();
|
|
669
|
+
if (!inside) {
|
|
670
|
+
throw new Error(`Invalid path (empty "[]"): ${raw}`);
|
|
671
|
+
}
|
|
672
|
+
parts.push(inside);
|
|
673
|
+
i = close + 1;
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
current += ch;
|
|
677
|
+
i += 1;
|
|
678
|
+
}
|
|
679
|
+
if (current) {
|
|
680
|
+
parts.push(current);
|
|
681
|
+
}
|
|
682
|
+
return parts.map((part) => part.trim()).filter(Boolean);
|
|
683
|
+
}
|
|
684
|
+
function parseRequiredConfigPath(raw) {
|
|
685
|
+
const parsedPath = parseConfigPath(raw);
|
|
686
|
+
if (parsedPath.length === 0) {
|
|
687
|
+
throw new Error("Path is empty.");
|
|
688
|
+
}
|
|
689
|
+
return parsedPath;
|
|
690
|
+
}
|
|
691
|
+
function parseConfigSetValue(raw, opts) {
|
|
692
|
+
const trimmed = raw.trim();
|
|
693
|
+
if (opts.json) {
|
|
694
|
+
return JSON.parse(trimmed);
|
|
695
|
+
}
|
|
696
|
+
try {
|
|
697
|
+
return JSON.parse(trimmed);
|
|
698
|
+
} catch {
|
|
699
|
+
return raw;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function getAtConfigPath(root, pathSegments) {
|
|
703
|
+
let current = root;
|
|
704
|
+
for (const segment of pathSegments) {
|
|
705
|
+
if (!current || typeof current !== "object") {
|
|
706
|
+
return { found: false };
|
|
707
|
+
}
|
|
708
|
+
if (Array.isArray(current)) {
|
|
709
|
+
if (!isIndexSegment(segment)) {
|
|
710
|
+
return { found: false };
|
|
711
|
+
}
|
|
712
|
+
const index = Number.parseInt(segment, 10);
|
|
713
|
+
if (!Number.isFinite(index) || index < 0 || index >= current.length) {
|
|
714
|
+
return { found: false };
|
|
715
|
+
}
|
|
716
|
+
current = current[index];
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const record = current;
|
|
720
|
+
if (!Object.prototype.hasOwnProperty.call(record, segment)) {
|
|
721
|
+
return { found: false };
|
|
722
|
+
}
|
|
723
|
+
current = record[segment];
|
|
724
|
+
}
|
|
725
|
+
return { found: true, value: current };
|
|
726
|
+
}
|
|
727
|
+
function setAtConfigPath(root, pathSegments, value) {
|
|
728
|
+
let current = root;
|
|
729
|
+
for (let i = 0; i < pathSegments.length - 1; i += 1) {
|
|
730
|
+
const segment = pathSegments[i];
|
|
731
|
+
const next = pathSegments[i + 1];
|
|
732
|
+
const nextIsIndex = Boolean(next && isIndexSegment(next));
|
|
733
|
+
if (Array.isArray(current)) {
|
|
734
|
+
if (!isIndexSegment(segment)) {
|
|
735
|
+
throw new Error(`Expected numeric index for array segment "${segment}"`);
|
|
736
|
+
}
|
|
737
|
+
const index = Number.parseInt(segment, 10);
|
|
738
|
+
const existing2 = current[index];
|
|
739
|
+
if (!existing2 || typeof existing2 !== "object") {
|
|
740
|
+
current[index] = nextIsIndex ? [] : {};
|
|
741
|
+
}
|
|
742
|
+
current = current[index];
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (!current || typeof current !== "object") {
|
|
746
|
+
throw new Error(`Cannot traverse into "${segment}" (not an object)`);
|
|
747
|
+
}
|
|
748
|
+
const record = current;
|
|
749
|
+
const existing = record[segment];
|
|
750
|
+
if (!existing || typeof existing !== "object") {
|
|
751
|
+
record[segment] = nextIsIndex ? [] : {};
|
|
752
|
+
}
|
|
753
|
+
current = record[segment];
|
|
754
|
+
}
|
|
755
|
+
const last = pathSegments[pathSegments.length - 1];
|
|
756
|
+
if (Array.isArray(current)) {
|
|
757
|
+
if (!isIndexSegment(last)) {
|
|
758
|
+
throw new Error(`Expected numeric index for array segment "${last}"`);
|
|
759
|
+
}
|
|
760
|
+
const index = Number.parseInt(last, 10);
|
|
761
|
+
current[index] = value;
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (!current || typeof current !== "object") {
|
|
765
|
+
throw new Error(`Cannot set "${last}" (parent is not an object)`);
|
|
766
|
+
}
|
|
767
|
+
current[last] = value;
|
|
768
|
+
}
|
|
769
|
+
function unsetAtConfigPath(root, pathSegments) {
|
|
770
|
+
let current = root;
|
|
771
|
+
for (let i = 0; i < pathSegments.length - 1; i += 1) {
|
|
772
|
+
const segment = pathSegments[i];
|
|
773
|
+
if (!current || typeof current !== "object") {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
if (Array.isArray(current)) {
|
|
777
|
+
if (!isIndexSegment(segment)) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
const index = Number.parseInt(segment, 10);
|
|
781
|
+
if (!Number.isFinite(index) || index < 0 || index >= current.length) {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
current = current[index];
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
const record2 = current;
|
|
788
|
+
if (!Object.prototype.hasOwnProperty.call(record2, segment)) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
current = record2[segment];
|
|
792
|
+
}
|
|
793
|
+
const last = pathSegments[pathSegments.length - 1];
|
|
794
|
+
if (Array.isArray(current)) {
|
|
795
|
+
if (!isIndexSegment(last)) {
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
const index = Number.parseInt(last, 10);
|
|
799
|
+
if (!Number.isFinite(index) || index < 0 || index >= current.length) {
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
current.splice(index, 1);
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
if (!current || typeof current !== "object") {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
const record = current;
|
|
809
|
+
if (!Object.prototype.hasOwnProperty.call(record, last)) {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
delete record[last];
|
|
813
|
+
return true;
|
|
814
|
+
}
|
|
630
815
|
var ConfigReloader = class {
|
|
631
816
|
constructor(options) {
|
|
632
817
|
this.options = options;
|
|
@@ -758,12 +943,12 @@ var CliRuntime = class {
|
|
|
758
943
|
const configPath = getConfigPath();
|
|
759
944
|
let createdConfig = false;
|
|
760
945
|
if (!existsSync4(configPath)) {
|
|
761
|
-
const
|
|
762
|
-
saveConfig(
|
|
946
|
+
const config3 = ConfigSchema2.parse({});
|
|
947
|
+
saveConfig(config3);
|
|
763
948
|
createdConfig = true;
|
|
764
949
|
}
|
|
765
|
-
const
|
|
766
|
-
const workspaceSetting =
|
|
950
|
+
const config2 = loadConfig();
|
|
951
|
+
const workspaceSetting = config2.agents.defaults.workspace;
|
|
767
952
|
const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join3(getDataDir2(), DEFAULT_WORKSPACE_DIR) : expandHome(workspaceSetting);
|
|
768
953
|
const workspaceExisted = existsSync4(workspacePath);
|
|
769
954
|
mkdirSync2(workspacePath, { recursive: true });
|
|
@@ -904,23 +1089,23 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
904
1089
|
await this.stopService();
|
|
905
1090
|
}
|
|
906
1091
|
async agent(opts) {
|
|
907
|
-
const
|
|
908
|
-
const workspace = getWorkspacePath(
|
|
909
|
-
const pluginRegistry = this.loadPluginRegistry(
|
|
1092
|
+
const config2 = loadConfig();
|
|
1093
|
+
const workspace = getWorkspacePath(config2.agents.defaults.workspace);
|
|
1094
|
+
const pluginRegistry = this.loadPluginRegistry(config2, workspace);
|
|
910
1095
|
const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
|
|
911
1096
|
this.logPluginDiagnostics(pluginRegistry);
|
|
912
1097
|
const bus = new MessageBus();
|
|
913
|
-
const provider = this.makeProvider(
|
|
1098
|
+
const provider = this.makeProvider(config2);
|
|
914
1099
|
const providerManager = new ProviderManager(provider);
|
|
915
1100
|
const agentLoop = new AgentLoop({
|
|
916
1101
|
bus,
|
|
917
1102
|
providerManager,
|
|
918
1103
|
workspace,
|
|
919
|
-
braveApiKey:
|
|
920
|
-
execConfig:
|
|
921
|
-
restrictToWorkspace:
|
|
922
|
-
contextConfig:
|
|
923
|
-
config,
|
|
1104
|
+
braveApiKey: config2.tools.web.search.apiKey || void 0,
|
|
1105
|
+
execConfig: config2.tools.exec,
|
|
1106
|
+
restrictToWorkspace: config2.tools.restrictToWorkspace,
|
|
1107
|
+
contextConfig: config2.agents.context,
|
|
1108
|
+
config: config2,
|
|
924
1109
|
extensionRegistry,
|
|
925
1110
|
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
926
1111
|
registry: pluginRegistry,
|
|
@@ -1006,12 +1191,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1006
1191
|
}
|
|
1007
1192
|
}
|
|
1008
1193
|
pluginsList(opts = {}) {
|
|
1009
|
-
const
|
|
1010
|
-
const workspaceDir = getWorkspacePath(
|
|
1194
|
+
const config2 = loadConfig();
|
|
1195
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
1011
1196
|
const report = buildPluginStatusReport({
|
|
1012
|
-
config,
|
|
1197
|
+
config: config2,
|
|
1013
1198
|
workspaceDir,
|
|
1014
|
-
reservedChannelIds: Object.keys(
|
|
1199
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
1015
1200
|
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1016
1201
|
});
|
|
1017
1202
|
const list = opts.enabled ? report.plugins.filter((plugin) => plugin.status === "loaded") : report.plugins;
|
|
@@ -1063,12 +1248,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1063
1248
|
}
|
|
1064
1249
|
}
|
|
1065
1250
|
pluginsInfo(id, opts = {}) {
|
|
1066
|
-
const
|
|
1067
|
-
const workspaceDir = getWorkspacePath(
|
|
1251
|
+
const config2 = loadConfig();
|
|
1252
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
1068
1253
|
const report = buildPluginStatusReport({
|
|
1069
|
-
config,
|
|
1254
|
+
config: config2,
|
|
1070
1255
|
workspaceDir,
|
|
1071
|
-
reservedChannelIds: Object.keys(
|
|
1256
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
1072
1257
|
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1073
1258
|
});
|
|
1074
1259
|
const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
|
|
@@ -1080,7 +1265,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1080
1265
|
console.log(JSON.stringify(plugin, null, 2));
|
|
1081
1266
|
return;
|
|
1082
1267
|
}
|
|
1083
|
-
const install =
|
|
1268
|
+
const install = config2.plugins.installs?.[plugin.id];
|
|
1084
1269
|
const lines = [];
|
|
1085
1270
|
lines.push(plugin.name || plugin.id);
|
|
1086
1271
|
if (plugin.name && plugin.name !== plugin.id) {
|
|
@@ -1129,25 +1314,98 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1129
1314
|
}
|
|
1130
1315
|
console.log(lines.join("\n"));
|
|
1131
1316
|
}
|
|
1317
|
+
configGet(pathExpr, opts = {}) {
|
|
1318
|
+
const config2 = loadConfig();
|
|
1319
|
+
let parsedPath;
|
|
1320
|
+
try {
|
|
1321
|
+
parsedPath = parseRequiredConfigPath(pathExpr);
|
|
1322
|
+
} catch (error) {
|
|
1323
|
+
console.error(String(error));
|
|
1324
|
+
process.exit(1);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
const result = getAtConfigPath(config2, parsedPath);
|
|
1328
|
+
if (!result.found) {
|
|
1329
|
+
console.error(`Config path not found: ${pathExpr}`);
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (opts.json) {
|
|
1334
|
+
console.log(JSON.stringify(result.value ?? null, null, 2));
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (typeof result.value === "string" || typeof result.value === "number" || typeof result.value === "boolean") {
|
|
1338
|
+
console.log(String(result.value));
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
console.log(JSON.stringify(result.value ?? null, null, 2));
|
|
1342
|
+
}
|
|
1343
|
+
configSet(pathExpr, value, opts = {}) {
|
|
1344
|
+
let parsedPath;
|
|
1345
|
+
try {
|
|
1346
|
+
parsedPath = parseRequiredConfigPath(pathExpr);
|
|
1347
|
+
} catch (error) {
|
|
1348
|
+
console.error(String(error));
|
|
1349
|
+
process.exit(1);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
let parsedValue;
|
|
1353
|
+
try {
|
|
1354
|
+
parsedValue = parseConfigSetValue(value, opts);
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
console.error(`Failed to parse config value: ${String(error)}`);
|
|
1357
|
+
process.exit(1);
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
const config2 = loadConfig();
|
|
1361
|
+
try {
|
|
1362
|
+
setAtConfigPath(config2, parsedPath, parsedValue);
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
console.error(String(error));
|
|
1365
|
+
process.exit(1);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
saveConfig(config2);
|
|
1369
|
+
console.log(`Updated ${pathExpr}. Restart the gateway to apply.`);
|
|
1370
|
+
}
|
|
1371
|
+
configUnset(pathExpr) {
|
|
1372
|
+
let parsedPath;
|
|
1373
|
+
try {
|
|
1374
|
+
parsedPath = parseRequiredConfigPath(pathExpr);
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
console.error(String(error));
|
|
1377
|
+
process.exit(1);
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
const config2 = loadConfig();
|
|
1381
|
+
const removed = unsetAtConfigPath(config2, parsedPath);
|
|
1382
|
+
if (!removed) {
|
|
1383
|
+
console.error(`Config path not found: ${pathExpr}`);
|
|
1384
|
+
process.exit(1);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
saveConfig(config2);
|
|
1388
|
+
console.log(`Removed ${pathExpr}. Restart the gateway to apply.`);
|
|
1389
|
+
}
|
|
1132
1390
|
pluginsEnable(id) {
|
|
1133
|
-
const
|
|
1134
|
-
const next = enablePluginInConfig(
|
|
1391
|
+
const config2 = loadConfig();
|
|
1392
|
+
const next = enablePluginInConfig(config2, id);
|
|
1135
1393
|
saveConfig(next);
|
|
1136
1394
|
console.log(`Enabled plugin "${id}". Restart the gateway to apply.`);
|
|
1137
1395
|
}
|
|
1138
1396
|
pluginsDisable(id) {
|
|
1139
|
-
const
|
|
1140
|
-
const next = disablePluginInConfig(
|
|
1397
|
+
const config2 = loadConfig();
|
|
1398
|
+
const next = disablePluginInConfig(config2, id);
|
|
1141
1399
|
saveConfig(next);
|
|
1142
1400
|
console.log(`Disabled plugin "${id}". Restart the gateway to apply.`);
|
|
1143
1401
|
}
|
|
1144
1402
|
async pluginsUninstall(id, opts = {}) {
|
|
1145
|
-
const
|
|
1146
|
-
const workspaceDir = getWorkspacePath(
|
|
1403
|
+
const config2 = loadConfig();
|
|
1404
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
1147
1405
|
const report = buildPluginStatusReport({
|
|
1148
|
-
config,
|
|
1406
|
+
config: config2,
|
|
1149
1407
|
workspaceDir,
|
|
1150
|
-
reservedChannelIds: Object.keys(
|
|
1408
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
1151
1409
|
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1152
1410
|
});
|
|
1153
1411
|
const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
|
|
@@ -1156,8 +1414,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1156
1414
|
}
|
|
1157
1415
|
const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
|
|
1158
1416
|
const pluginId = plugin?.id ?? id;
|
|
1159
|
-
const hasEntry = pluginId in (
|
|
1160
|
-
const hasInstall = pluginId in (
|
|
1417
|
+
const hasEntry = pluginId in (config2.plugins.entries ?? {});
|
|
1418
|
+
const hasInstall = pluginId in (config2.plugins.installs ?? {});
|
|
1161
1419
|
if (!hasEntry && !hasInstall) {
|
|
1162
1420
|
if (plugin) {
|
|
1163
1421
|
console.error(
|
|
@@ -1168,8 +1426,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1168
1426
|
}
|
|
1169
1427
|
process.exit(1);
|
|
1170
1428
|
}
|
|
1171
|
-
const install =
|
|
1172
|
-
const isLinked = install?.source === "path";
|
|
1429
|
+
const install = config2.plugins.installs?.[pluginId];
|
|
1430
|
+
const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve4(install.installPath) === resolve4(install.sourcePath));
|
|
1173
1431
|
const preview = [];
|
|
1174
1432
|
if (hasEntry) {
|
|
1175
1433
|
preview.push("config entry");
|
|
@@ -1177,10 +1435,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1177
1435
|
if (hasInstall) {
|
|
1178
1436
|
preview.push("install record");
|
|
1179
1437
|
}
|
|
1180
|
-
if (
|
|
1438
|
+
if (config2.plugins.allow?.includes(pluginId)) {
|
|
1181
1439
|
preview.push("allowlist entry");
|
|
1182
1440
|
}
|
|
1183
|
-
if (isLinked && install?.sourcePath &&
|
|
1441
|
+
if (isLinked && install?.sourcePath && config2.plugins.load?.paths?.includes(install.sourcePath)) {
|
|
1184
1442
|
preview.push("load path");
|
|
1185
1443
|
}
|
|
1186
1444
|
const deleteTarget = !keepFiles ? resolveUninstallDirectoryTarget({
|
|
@@ -1207,7 +1465,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1207
1465
|
}
|
|
1208
1466
|
}
|
|
1209
1467
|
const result = await uninstallPlugin({
|
|
1210
|
-
config,
|
|
1468
|
+
config: config2,
|
|
1211
1469
|
pluginId,
|
|
1212
1470
|
deleteFiles: !keepFiles
|
|
1213
1471
|
});
|
|
@@ -1246,7 +1504,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1246
1504
|
}
|
|
1247
1505
|
const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
|
|
1248
1506
|
const resolved = resolve4(expandHome(normalized));
|
|
1249
|
-
const
|
|
1507
|
+
const config2 = loadConfig();
|
|
1250
1508
|
if (existsSync4(resolved)) {
|
|
1251
1509
|
if (opts.link) {
|
|
1252
1510
|
const probe = await installPluginFromPath({ path: resolved, dryRun: true });
|
|
@@ -1254,7 +1512,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1254
1512
|
console.error(probe.error);
|
|
1255
1513
|
process.exit(1);
|
|
1256
1514
|
}
|
|
1257
|
-
let next3 = addPluginLoadPath(
|
|
1515
|
+
let next3 = addPluginLoadPath(config2, resolved);
|
|
1258
1516
|
next3 = enablePluginInConfig(next3, probe.pluginId);
|
|
1259
1517
|
next3 = recordPluginInstall(next3, {
|
|
1260
1518
|
pluginId: probe.pluginId,
|
|
@@ -1279,7 +1537,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1279
1537
|
console.error(result2.error);
|
|
1280
1538
|
process.exit(1);
|
|
1281
1539
|
}
|
|
1282
|
-
let next2 = enablePluginInConfig(
|
|
1540
|
+
let next2 = enablePluginInConfig(config2, result2.pluginId);
|
|
1283
1541
|
next2 = recordPluginInstall(next2, {
|
|
1284
1542
|
pluginId: result2.pluginId,
|
|
1285
1543
|
source: this.isArchivePath(resolved) ? "archive" : "path",
|
|
@@ -1311,7 +1569,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1311
1569
|
console.error(result.error);
|
|
1312
1570
|
process.exit(1);
|
|
1313
1571
|
}
|
|
1314
|
-
let next = enablePluginInConfig(
|
|
1572
|
+
let next = enablePluginInConfig(config2, result.pluginId);
|
|
1315
1573
|
next = recordPluginInstall(next, {
|
|
1316
1574
|
pluginId: result.pluginId,
|
|
1317
1575
|
source: "npm",
|
|
@@ -1324,12 +1582,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1324
1582
|
console.log("Restart the gateway to load plugins.");
|
|
1325
1583
|
}
|
|
1326
1584
|
pluginsDoctor() {
|
|
1327
|
-
const
|
|
1328
|
-
const workspaceDir = getWorkspacePath(
|
|
1585
|
+
const config2 = loadConfig();
|
|
1586
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
1329
1587
|
const report = buildPluginStatusReport({
|
|
1330
|
-
config,
|
|
1588
|
+
config: config2,
|
|
1331
1589
|
workspaceDir,
|
|
1332
|
-
reservedChannelIds: Object.keys(
|
|
1590
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
1333
1591
|
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1334
1592
|
});
|
|
1335
1593
|
const pluginErrors = report.plugins.filter((plugin) => plugin.status === "error");
|
|
@@ -1377,20 +1635,20 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1377
1635
|
console.log(` Path: ${result.destinationDir}`);
|
|
1378
1636
|
}
|
|
1379
1637
|
channelsStatus() {
|
|
1380
|
-
const
|
|
1638
|
+
const config2 = loadConfig();
|
|
1381
1639
|
console.log("Channel Status");
|
|
1382
|
-
console.log(`WhatsApp: ${
|
|
1383
|
-
console.log(`Discord: ${
|
|
1384
|
-
console.log(`Feishu: ${
|
|
1385
|
-
console.log(`Mochat: ${
|
|
1386
|
-
console.log(`Telegram: ${
|
|
1387
|
-
console.log(`Slack: ${
|
|
1388
|
-
console.log(`QQ: ${
|
|
1389
|
-
const workspaceDir = getWorkspacePath(
|
|
1640
|
+
console.log(`WhatsApp: ${config2.channels.whatsapp.enabled ? "\u2713" : "\u2717"}`);
|
|
1641
|
+
console.log(`Discord: ${config2.channels.discord.enabled ? "\u2713" : "\u2717"}`);
|
|
1642
|
+
console.log(`Feishu: ${config2.channels.feishu.enabled ? "\u2713" : "\u2717"}`);
|
|
1643
|
+
console.log(`Mochat: ${config2.channels.mochat.enabled ? "\u2713" : "\u2717"}`);
|
|
1644
|
+
console.log(`Telegram: ${config2.channels.telegram.enabled ? "\u2713" : "\u2717"}`);
|
|
1645
|
+
console.log(`Slack: ${config2.channels.slack.enabled ? "\u2713" : "\u2717"}`);
|
|
1646
|
+
console.log(`QQ: ${config2.channels.qq.enabled ? "\u2713" : "\u2717"}`);
|
|
1647
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
1390
1648
|
const report = buildPluginStatusReport({
|
|
1391
|
-
config,
|
|
1649
|
+
config: config2,
|
|
1392
1650
|
workspaceDir,
|
|
1393
|
-
reservedChannelIds: Object.keys(
|
|
1651
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
1394
1652
|
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
1395
1653
|
});
|
|
1396
1654
|
const pluginChannels = report.plugins.filter((plugin) => plugin.status === "loaded" && plugin.channelIds.length > 0);
|
|
@@ -1417,9 +1675,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1417
1675
|
console.error("--channel is required");
|
|
1418
1676
|
process.exit(1);
|
|
1419
1677
|
}
|
|
1420
|
-
const
|
|
1421
|
-
const workspaceDir = getWorkspacePath(
|
|
1422
|
-
const pluginRegistry = this.loadPluginRegistry(
|
|
1678
|
+
const config2 = loadConfig();
|
|
1679
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
1680
|
+
const pluginRegistry = this.loadPluginRegistry(config2, workspaceDir);
|
|
1423
1681
|
const bindings = getPluginChannelBindings(pluginRegistry);
|
|
1424
1682
|
const binding = bindings.find((entry) => entry.channelId === channelId || entry.pluginId === channelId);
|
|
1425
1683
|
if (!binding) {
|
|
@@ -1438,7 +1696,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1438
1696
|
url: opts.url,
|
|
1439
1697
|
httpUrl: opts.httpUrl
|
|
1440
1698
|
};
|
|
1441
|
-
const currentView = this.toPluginConfigView(
|
|
1699
|
+
const currentView = this.toPluginConfigView(config2, bindings);
|
|
1442
1700
|
const accountId = binding.channel.config?.defaultAccountId?.(currentView) ?? "default";
|
|
1443
1701
|
const validateError = setup.validateInput?.({
|
|
1444
1702
|
cfg: currentView,
|
|
@@ -1458,17 +1716,17 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1458
1716
|
console.error("Channel setup returned invalid config payload.");
|
|
1459
1717
|
process.exit(1);
|
|
1460
1718
|
}
|
|
1461
|
-
let next = this.mergePluginConfigView(
|
|
1719
|
+
let next = this.mergePluginConfigView(config2, nextView, bindings);
|
|
1462
1720
|
next = enablePluginInConfig(next, binding.pluginId);
|
|
1463
1721
|
saveConfig(next);
|
|
1464
1722
|
console.log(`Configured channel "${binding.channelId}" via plugin "${binding.pluginId}".`);
|
|
1465
1723
|
console.log("Restart the gateway to apply changes.");
|
|
1466
1724
|
}
|
|
1467
|
-
toPluginConfigView(
|
|
1468
|
-
const view = JSON.parse(JSON.stringify(
|
|
1725
|
+
toPluginConfigView(config2, bindings) {
|
|
1726
|
+
const view = JSON.parse(JSON.stringify(config2));
|
|
1469
1727
|
const channels2 = view.channels && typeof view.channels === "object" && !Array.isArray(view.channels) ? { ...view.channels } : {};
|
|
1470
1728
|
for (const binding of bindings) {
|
|
1471
|
-
const pluginConfig =
|
|
1729
|
+
const pluginConfig = config2.plugins.entries?.[binding.pluginId]?.config;
|
|
1472
1730
|
if (!pluginConfig || typeof pluginConfig !== "object" || Array.isArray(pluginConfig)) {
|
|
1473
1731
|
continue;
|
|
1474
1732
|
}
|
|
@@ -1572,15 +1830,15 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1572
1830
|
}
|
|
1573
1831
|
status() {
|
|
1574
1832
|
const configPath = getConfigPath();
|
|
1575
|
-
const
|
|
1576
|
-
const workspace = getWorkspacePath(
|
|
1833
|
+
const config2 = loadConfig();
|
|
1834
|
+
const workspace = getWorkspacePath(config2.agents.defaults.workspace);
|
|
1577
1835
|
console.log(`${this.logo} ${APP_NAME} Status
|
|
1578
1836
|
`);
|
|
1579
1837
|
console.log(`Config: ${configPath} ${existsSync4(configPath) ? "\u2713" : "\u2717"}`);
|
|
1580
1838
|
console.log(`Workspace: ${workspace} ${existsSync4(workspace) ? "\u2713" : "\u2717"}`);
|
|
1581
|
-
console.log(`Model: ${
|
|
1839
|
+
console.log(`Model: ${config2.agents.defaults.model}`);
|
|
1582
1840
|
for (const spec of PROVIDERS) {
|
|
1583
|
-
const provider =
|
|
1841
|
+
const provider = config2.providers[spec.name];
|
|
1584
1842
|
if (!provider) {
|
|
1585
1843
|
continue;
|
|
1586
1844
|
}
|
|
@@ -1591,9 +1849,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1591
1849
|
}
|
|
1592
1850
|
}
|
|
1593
1851
|
}
|
|
1594
|
-
loadPluginRegistry(
|
|
1852
|
+
loadPluginRegistry(config2, workspaceDir) {
|
|
1595
1853
|
return loadOpenClawPlugins({
|
|
1596
|
-
config,
|
|
1854
|
+
config: config2,
|
|
1597
1855
|
workspaceDir,
|
|
1598
1856
|
reservedToolNames: [
|
|
1599
1857
|
"read_file",
|
|
@@ -1614,7 +1872,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1614
1872
|
"gateway",
|
|
1615
1873
|
"cron"
|
|
1616
1874
|
],
|
|
1617
|
-
reservedChannelIds: Object.keys(
|
|
1875
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
1618
1876
|
reservedProviderIds: PROVIDERS.map((provider) => provider.name),
|
|
1619
1877
|
logger: {
|
|
1620
1878
|
info: (message) => console.log(message),
|
|
@@ -1658,19 +1916,19 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1658
1916
|
}
|
|
1659
1917
|
}
|
|
1660
1918
|
async startGateway(options = {}) {
|
|
1661
|
-
const
|
|
1662
|
-
const workspace = getWorkspacePath(
|
|
1663
|
-
const pluginRegistry = this.loadPluginRegistry(
|
|
1919
|
+
const config2 = loadConfig();
|
|
1920
|
+
const workspace = getWorkspacePath(config2.agents.defaults.workspace);
|
|
1921
|
+
const pluginRegistry = this.loadPluginRegistry(config2, workspace);
|
|
1664
1922
|
const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
|
|
1665
1923
|
this.logPluginDiagnostics(pluginRegistry);
|
|
1666
1924
|
const bus = new MessageBus();
|
|
1667
|
-
const provider = options.allowMissingProvider === true ? this.makeProvider(
|
|
1925
|
+
const provider = options.allowMissingProvider === true ? this.makeProvider(config2, { allowMissing: true }) : this.makeProvider(config2);
|
|
1668
1926
|
const providerManager = provider ? new ProviderManager(provider) : null;
|
|
1669
1927
|
const sessionManager = new SessionManager(workspace);
|
|
1670
1928
|
const cronStorePath = join3(getDataDir2(), "cron", "jobs.json");
|
|
1671
1929
|
const cron2 = new CronService(cronStorePath);
|
|
1672
1930
|
const pluginUiMetadata = getPluginUiMetadataFromRegistry(pluginRegistry);
|
|
1673
|
-
const uiConfig = resolveUiConfig(
|
|
1931
|
+
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
1674
1932
|
const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
|
|
1675
1933
|
if (!provider) {
|
|
1676
1934
|
this.startUiIfEnabled(uiConfig, uiStaticDir);
|
|
@@ -1679,9 +1937,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1679
1937
|
});
|
|
1680
1938
|
return;
|
|
1681
1939
|
}
|
|
1682
|
-
const channels2 = new ChannelManager(
|
|
1940
|
+
const channels2 = new ChannelManager(config2, bus, sessionManager, extensionRegistry.channels);
|
|
1683
1941
|
const reloader = new ConfigReloader({
|
|
1684
|
-
initialConfig:
|
|
1942
|
+
initialConfig: config2,
|
|
1685
1943
|
channels: channels2,
|
|
1686
1944
|
bus,
|
|
1687
1945
|
sessionManager,
|
|
@@ -1704,16 +1962,16 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1704
1962
|
bus,
|
|
1705
1963
|
providerManager: providerManager ?? new ProviderManager(provider),
|
|
1706
1964
|
workspace,
|
|
1707
|
-
model:
|
|
1708
|
-
maxIterations:
|
|
1709
|
-
braveApiKey:
|
|
1710
|
-
execConfig:
|
|
1965
|
+
model: config2.agents.defaults.model,
|
|
1966
|
+
maxIterations: config2.agents.defaults.maxToolIterations,
|
|
1967
|
+
braveApiKey: config2.tools.web.search.apiKey || void 0,
|
|
1968
|
+
execConfig: config2.tools.exec,
|
|
1711
1969
|
cronService: cron2,
|
|
1712
|
-
restrictToWorkspace:
|
|
1970
|
+
restrictToWorkspace: config2.tools.restrictToWorkspace,
|
|
1713
1971
|
sessionManager,
|
|
1714
|
-
contextConfig:
|
|
1972
|
+
contextConfig: config2.agents.context,
|
|
1715
1973
|
gatewayController,
|
|
1716
|
-
config,
|
|
1974
|
+
config: config2,
|
|
1717
1975
|
extensionRegistry,
|
|
1718
1976
|
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
1719
1977
|
registry: pluginRegistry,
|
|
@@ -1853,8 +2111,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1853
2111
|
}
|
|
1854
2112
|
}
|
|
1855
2113
|
async runForeground(options) {
|
|
1856
|
-
const
|
|
1857
|
-
const uiConfig = resolveUiConfig(
|
|
2114
|
+
const config2 = loadConfig();
|
|
2115
|
+
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
1858
2116
|
const shouldStartFrontend = options.frontend;
|
|
1859
2117
|
const frontendPort = Number.isFinite(options.frontendPort) ? options.frontendPort : 5173;
|
|
1860
2118
|
const frontendDir = shouldStartFrontend ? resolveUiFrontendDir() : null;
|
|
@@ -1886,8 +2144,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1886
2144
|
});
|
|
1887
2145
|
}
|
|
1888
2146
|
async startService(options) {
|
|
1889
|
-
const
|
|
1890
|
-
const uiConfig = resolveUiConfig(
|
|
2147
|
+
const config2 = loadConfig();
|
|
2148
|
+
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
1891
2149
|
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
1892
2150
|
const apiUrl = `${uiUrl}/api`;
|
|
1893
2151
|
const staticDir = resolveUiStaticDir();
|
|
@@ -1987,9 +2245,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
1987
2245
|
const normalized = answer.trim().toLowerCase();
|
|
1988
2246
|
return normalized === "y" || normalized === "yes";
|
|
1989
2247
|
}
|
|
1990
|
-
makeProvider(
|
|
1991
|
-
const provider = getProvider(
|
|
1992
|
-
const model =
|
|
2248
|
+
makeProvider(config2, options) {
|
|
2249
|
+
const provider = getProvider(config2);
|
|
2250
|
+
const model = config2.agents.defaults.model;
|
|
1993
2251
|
if (!provider?.apiKey && !model.startsWith("bedrock/")) {
|
|
1994
2252
|
if (options?.allowMissing) {
|
|
1995
2253
|
return null;
|
|
@@ -2000,10 +2258,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
|
|
|
2000
2258
|
}
|
|
2001
2259
|
return new LiteLLMProvider({
|
|
2002
2260
|
apiKey: provider?.apiKey ?? null,
|
|
2003
|
-
apiBase: getApiBase(
|
|
2261
|
+
apiBase: getApiBase(config2),
|
|
2004
2262
|
defaultModel: model,
|
|
2005
2263
|
extraHeaders: provider?.extraHeaders ?? null,
|
|
2006
|
-
providerName: getProviderName(
|
|
2264
|
+
providerName: getProviderName(config2),
|
|
2007
2265
|
wireApi: provider?.wireApi ?? null
|
|
2008
2266
|
});
|
|
2009
2267
|
}
|
|
@@ -2231,6 +2489,10 @@ plugins.command("disable <id>").description("Disable a plugin in config").action
|
|
|
2231
2489
|
plugins.command("uninstall <id>").description("Uninstall a plugin").option("--keep-files", "Keep installed files on disk", false).option("--keep-config", "Deprecated alias for --keep-files", false).option("--force", "Skip confirmation prompt", false).option("--dry-run", "Show what would be removed without making changes", false).action(async (id, opts) => runtime.pluginsUninstall(id, opts));
|
|
2232
2490
|
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));
|
|
2233
2491
|
plugins.command("doctor").description("Report plugin load issues").action(() => runtime.pluginsDoctor());
|
|
2492
|
+
var config = program.command("config").description("Manage config values");
|
|
2493
|
+
config.command("get <path>").description("Get a config value by dot path").option("--json", "Output JSON", false).action((path, opts) => runtime.configGet(path, opts));
|
|
2494
|
+
config.command("set <path> <value>").description("Set a config value by dot path").option("--json", "Parse value as JSON", false).action((path, value, opts) => runtime.configSet(path, value, opts));
|
|
2495
|
+
config.command("unset <path>").description("Remove a config value by dot path").action((path) => runtime.configUnset(path));
|
|
2234
2496
|
var channels = program.command("channels").description("Manage channels");
|
|
2235
2497
|
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));
|
|
2236
2498
|
channels.command("status").description("Show channel status").action(() => runtime.channelsStatus());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextclaw",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
4
4
|
"description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"dev": "tsx watch --tsconfig tsconfig.json src/cli/index.ts",
|
|
59
|
-
"dev:build": "
|
|
60
|
-
"build": "
|
|
59
|
+
"dev:build": "tsx src/cli/index.ts",
|
|
60
|
+
"build": "tsup src/index.ts src/cli/index.ts --format esm --dts --out-dir dist && node scripts/copy-ui-dist.mjs",
|
|
61
61
|
"start": "node dist/cli.js",
|
|
62
62
|
"lint": "eslint .",
|
|
63
63
|
"tsc": "tsc -p tsconfig.json",
|