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.
Files changed (2) hide show
  1. package/dist/cli/index.js +364 -102
  2. 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(config, overrides) {
88
- const base = config.ui ?? { enabled: false, host: "127.0.0.1", port: 18791, open: false };
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 config;
392
+ let config2;
393
393
  let valid = true;
394
394
  try {
395
- config = ConfigSchema.parse(parsed);
395
+ config2 = ConfigSchema.parse(parsed);
396
396
  } catch {
397
- config = ConfigSchema.parse({});
397
+ config2 = ConfigSchema.parse({});
398
398
  valid = false;
399
399
  }
400
400
  if (!raw) {
401
- raw = JSON.stringify(config, null, 2);
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(config, schema.uiHints);
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 config2 = ConfigSchema2.parse({});
762
- saveConfig(config2);
946
+ const config3 = ConfigSchema2.parse({});
947
+ saveConfig(config3);
763
948
  createdConfig = true;
764
949
  }
765
- const config = loadConfig();
766
- const workspaceSetting = config.agents.defaults.workspace;
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 config = loadConfig();
908
- const workspace = getWorkspacePath(config.agents.defaults.workspace);
909
- const pluginRegistry = this.loadPluginRegistry(config, workspace);
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(config);
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: config.tools.web.search.apiKey || void 0,
920
- execConfig: config.tools.exec,
921
- restrictToWorkspace: config.tools.restrictToWorkspace,
922
- contextConfig: config.agents.context,
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 config = loadConfig();
1010
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
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(config.channels),
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 config = loadConfig();
1067
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
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(config.channels),
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 = config.plugins.installs?.[plugin.id];
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 config = loadConfig();
1134
- const next = enablePluginInConfig(config, id);
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 config = loadConfig();
1140
- const next = disablePluginInConfig(config, id);
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 config = loadConfig();
1146
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
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(config.channels),
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 (config.plugins.entries ?? {});
1160
- const hasInstall = pluginId in (config.plugins.installs ?? {});
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 = config.plugins.installs?.[pluginId];
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 (config.plugins.allow?.includes(pluginId)) {
1438
+ if (config2.plugins.allow?.includes(pluginId)) {
1181
1439
  preview.push("allowlist entry");
1182
1440
  }
1183
- if (isLinked && install?.sourcePath && config.plugins.load?.paths?.includes(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 config = loadConfig();
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(config, resolved);
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(config, result2.pluginId);
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(config, result.pluginId);
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 config = loadConfig();
1328
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
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(config.channels),
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 config = loadConfig();
1638
+ const config2 = loadConfig();
1381
1639
  console.log("Channel Status");
1382
- console.log(`WhatsApp: ${config.channels.whatsapp.enabled ? "\u2713" : "\u2717"}`);
1383
- console.log(`Discord: ${config.channels.discord.enabled ? "\u2713" : "\u2717"}`);
1384
- console.log(`Feishu: ${config.channels.feishu.enabled ? "\u2713" : "\u2717"}`);
1385
- console.log(`Mochat: ${config.channels.mochat.enabled ? "\u2713" : "\u2717"}`);
1386
- console.log(`Telegram: ${config.channels.telegram.enabled ? "\u2713" : "\u2717"}`);
1387
- console.log(`Slack: ${config.channels.slack.enabled ? "\u2713" : "\u2717"}`);
1388
- console.log(`QQ: ${config.channels.qq.enabled ? "\u2713" : "\u2717"}`);
1389
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
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(config.channels),
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 config = loadConfig();
1421
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1422
- const pluginRegistry = this.loadPluginRegistry(config, workspaceDir);
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(config, bindings);
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(config, nextView, bindings);
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(config, bindings) {
1468
- const view = JSON.parse(JSON.stringify(config));
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 = config.plugins.entries?.[binding.pluginId]?.config;
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 config = loadConfig();
1576
- const workspace = getWorkspacePath(config.agents.defaults.workspace);
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: ${config.agents.defaults.model}`);
1839
+ console.log(`Model: ${config2.agents.defaults.model}`);
1582
1840
  for (const spec of PROVIDERS) {
1583
- const provider = config.providers[spec.name];
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(config, workspaceDir) {
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(config.channels),
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 config = loadConfig();
1662
- const workspace = getWorkspacePath(config.agents.defaults.workspace);
1663
- const pluginRegistry = this.loadPluginRegistry(config, workspace);
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(config, { allowMissing: true }) : this.makeProvider(config);
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(config, options.uiOverrides);
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(config, bus, sessionManager, extensionRegistry.channels);
1940
+ const channels2 = new ChannelManager(config2, bus, sessionManager, extensionRegistry.channels);
1683
1941
  const reloader = new ConfigReloader({
1684
- initialConfig: config,
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: config.agents.defaults.model,
1708
- maxIterations: config.agents.defaults.maxToolIterations,
1709
- braveApiKey: config.tools.web.search.apiKey || void 0,
1710
- execConfig: config.tools.exec,
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: config.tools.restrictToWorkspace,
1970
+ restrictToWorkspace: config2.tools.restrictToWorkspace,
1713
1971
  sessionManager,
1714
- contextConfig: config.agents.context,
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 config = loadConfig();
1857
- const uiConfig = resolveUiConfig(config, options.uiOverrides);
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 config = loadConfig();
1890
- const uiConfig = resolveUiConfig(config, options.uiOverrides);
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(config, options) {
1991
- const provider = getProvider(config);
1992
- const model = config.agents.defaults.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(config),
2261
+ apiBase: getApiBase(config2),
2004
2262
  defaultModel: model,
2005
2263
  extraHeaders: provider?.extraHeaders ?? null,
2006
- providerName: getProviderName(config),
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.12",
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": "pnpm -C ../nextclaw-core build && pnpm -C ../nextclaw-openclaw-compat build && pnpm -C ../nextclaw-server build && tsx src/cli/index.ts",
60
- "build": "pnpm -C ../nextclaw-core build && pnpm -C ../nextclaw-openclaw-compat build && pnpm -C ../nextclaw-server build && tsup src/index.ts src/cli/index.ts --format esm --dts --out-dir dist && node scripts/copy-ui-dist.mjs",
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",