nextclaw 0.4.11 → 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 +393 -103
  2. package/package.json +4 -4
package/dist/cli/index.js CHANGED
@@ -83,9 +83,9 @@ import { join, resolve } from "path";
83
83
  import { spawn } from "child_process";
84
84
  import { createServer } from "net";
85
85
  import { fileURLToPath } from "url";
86
- import { getDataDir, getPackageVersion } from "@nextclaw/core";
87
- function resolveUiConfig(config, overrides) {
88
- const base = config.ui ?? { enabled: false, host: "127.0.0.1", port: 18791, open: false };
86
+ import { getDataDir, getPackageVersion as getCorePackageVersion } from "@nextclaw/core";
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) {
@@ -234,6 +234,34 @@ function which(binary) {
234
234
  }
235
235
  return false;
236
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
+ }
237
265
  function startUiFrontend(options) {
238
266
  const uiDir = options.dir ?? resolveUiFrontendDir();
239
267
  if (!uiDir) {
@@ -361,21 +389,21 @@ var readConfigSnapshot = (getConfigPath2, plugins2) => {
361
389
  parsed = {};
362
390
  }
363
391
  }
364
- let config;
392
+ let config2;
365
393
  let valid = true;
366
394
  try {
367
- config = ConfigSchema.parse(parsed);
395
+ config2 = ConfigSchema.parse(parsed);
368
396
  } catch {
369
- config = ConfigSchema.parse({});
397
+ config2 = ConfigSchema.parse({});
370
398
  valid = false;
371
399
  }
372
400
  if (!raw) {
373
- raw = JSON.stringify(config, null, 2);
401
+ raw = JSON.stringify(config2, null, 2);
374
402
  }
375
403
  const hash = hashRaw(raw);
376
404
  const schema = buildConfigSchema({ version: getPackageVersion(), plugins: plugins2 });
377
- const redacted = redactConfigObject(config, schema.uiHints);
378
- 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 };
379
407
  };
380
408
  var redactValue = (value, plugins2) => {
381
409
  const schema = buildConfigSchema({ version: getPackageVersion(), plugins: plugins2 });
@@ -599,6 +627,191 @@ function buildClawHubArgs(slug, options) {
599
627
  // src/cli/runtime.ts
600
628
  var LOGO = "\u{1F916}";
601
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
+ }
602
815
  var ConfigReloader = class {
603
816
  constructor(options) {
604
817
  this.options = options;
@@ -730,12 +943,12 @@ var CliRuntime = class {
730
943
  const configPath = getConfigPath();
731
944
  let createdConfig = false;
732
945
  if (!existsSync4(configPath)) {
733
- const config2 = ConfigSchema2.parse({});
734
- saveConfig(config2);
946
+ const config3 = ConfigSchema2.parse({});
947
+ saveConfig(config3);
735
948
  createdConfig = true;
736
949
  }
737
- const config = loadConfig();
738
- const workspaceSetting = config.agents.defaults.workspace;
950
+ const config2 = loadConfig();
951
+ const workspaceSetting = config2.agents.defaults.workspace;
739
952
  const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join3(getDataDir2(), DEFAULT_WORKSPACE_DIR) : expandHome(workspaceSetting);
740
953
  const workspaceExisted = existsSync4(workspacePath);
741
954
  mkdirSync2(workspacePath, { recursive: true });
@@ -876,23 +1089,23 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
876
1089
  await this.stopService();
877
1090
  }
878
1091
  async agent(opts) {
879
- const config = loadConfig();
880
- const workspace = getWorkspacePath(config.agents.defaults.workspace);
881
- 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);
882
1095
  const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
883
1096
  this.logPluginDiagnostics(pluginRegistry);
884
1097
  const bus = new MessageBus();
885
- const provider = this.makeProvider(config);
1098
+ const provider = this.makeProvider(config2);
886
1099
  const providerManager = new ProviderManager(provider);
887
1100
  const agentLoop = new AgentLoop({
888
1101
  bus,
889
1102
  providerManager,
890
1103
  workspace,
891
- braveApiKey: config.tools.web.search.apiKey || void 0,
892
- execConfig: config.tools.exec,
893
- restrictToWorkspace: config.tools.restrictToWorkspace,
894
- contextConfig: config.agents.context,
895
- 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,
896
1109
  extensionRegistry,
897
1110
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
898
1111
  registry: pluginRegistry,
@@ -978,12 +1191,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
978
1191
  }
979
1192
  }
980
1193
  pluginsList(opts = {}) {
981
- const config = loadConfig();
982
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1194
+ const config2 = loadConfig();
1195
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
983
1196
  const report = buildPluginStatusReport({
984
- config,
1197
+ config: config2,
985
1198
  workspaceDir,
986
- reservedChannelIds: Object.keys(config.channels),
1199
+ reservedChannelIds: Object.keys(config2.channels),
987
1200
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
988
1201
  });
989
1202
  const list = opts.enabled ? report.plugins.filter((plugin) => plugin.status === "loaded") : report.plugins;
@@ -1035,12 +1248,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1035
1248
  }
1036
1249
  }
1037
1250
  pluginsInfo(id, opts = {}) {
1038
- const config = loadConfig();
1039
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1251
+ const config2 = loadConfig();
1252
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1040
1253
  const report = buildPluginStatusReport({
1041
- config,
1254
+ config: config2,
1042
1255
  workspaceDir,
1043
- reservedChannelIds: Object.keys(config.channels),
1256
+ reservedChannelIds: Object.keys(config2.channels),
1044
1257
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
1045
1258
  });
1046
1259
  const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
@@ -1052,7 +1265,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1052
1265
  console.log(JSON.stringify(plugin, null, 2));
1053
1266
  return;
1054
1267
  }
1055
- const install = config.plugins.installs?.[plugin.id];
1268
+ const install = config2.plugins.installs?.[plugin.id];
1056
1269
  const lines = [];
1057
1270
  lines.push(plugin.name || plugin.id);
1058
1271
  if (plugin.name && plugin.name !== plugin.id) {
@@ -1101,25 +1314,98 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1101
1314
  }
1102
1315
  console.log(lines.join("\n"));
1103
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
+ }
1104
1390
  pluginsEnable(id) {
1105
- const config = loadConfig();
1106
- const next = enablePluginInConfig(config, id);
1391
+ const config2 = loadConfig();
1392
+ const next = enablePluginInConfig(config2, id);
1107
1393
  saveConfig(next);
1108
1394
  console.log(`Enabled plugin "${id}". Restart the gateway to apply.`);
1109
1395
  }
1110
1396
  pluginsDisable(id) {
1111
- const config = loadConfig();
1112
- const next = disablePluginInConfig(config, id);
1397
+ const config2 = loadConfig();
1398
+ const next = disablePluginInConfig(config2, id);
1113
1399
  saveConfig(next);
1114
1400
  console.log(`Disabled plugin "${id}". Restart the gateway to apply.`);
1115
1401
  }
1116
1402
  async pluginsUninstall(id, opts = {}) {
1117
- const config = loadConfig();
1118
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1403
+ const config2 = loadConfig();
1404
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1119
1405
  const report = buildPluginStatusReport({
1120
- config,
1406
+ config: config2,
1121
1407
  workspaceDir,
1122
- reservedChannelIds: Object.keys(config.channels),
1408
+ reservedChannelIds: Object.keys(config2.channels),
1123
1409
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
1124
1410
  });
1125
1411
  const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
@@ -1128,8 +1414,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1128
1414
  }
1129
1415
  const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
1130
1416
  const pluginId = plugin?.id ?? id;
1131
- const hasEntry = pluginId in (config.plugins.entries ?? {});
1132
- const hasInstall = pluginId in (config.plugins.installs ?? {});
1417
+ const hasEntry = pluginId in (config2.plugins.entries ?? {});
1418
+ const hasInstall = pluginId in (config2.plugins.installs ?? {});
1133
1419
  if (!hasEntry && !hasInstall) {
1134
1420
  if (plugin) {
1135
1421
  console.error(
@@ -1140,8 +1426,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1140
1426
  }
1141
1427
  process.exit(1);
1142
1428
  }
1143
- const install = config.plugins.installs?.[pluginId];
1144
- 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));
1145
1431
  const preview = [];
1146
1432
  if (hasEntry) {
1147
1433
  preview.push("config entry");
@@ -1149,10 +1435,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1149
1435
  if (hasInstall) {
1150
1436
  preview.push("install record");
1151
1437
  }
1152
- if (config.plugins.allow?.includes(pluginId)) {
1438
+ if (config2.plugins.allow?.includes(pluginId)) {
1153
1439
  preview.push("allowlist entry");
1154
1440
  }
1155
- if (isLinked && install?.sourcePath && config.plugins.load?.paths?.includes(install.sourcePath)) {
1441
+ if (isLinked && install?.sourcePath && config2.plugins.load?.paths?.includes(install.sourcePath)) {
1156
1442
  preview.push("load path");
1157
1443
  }
1158
1444
  const deleteTarget = !keepFiles ? resolveUninstallDirectoryTarget({
@@ -1179,7 +1465,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1179
1465
  }
1180
1466
  }
1181
1467
  const result = await uninstallPlugin({
1182
- config,
1468
+ config: config2,
1183
1469
  pluginId,
1184
1470
  deleteFiles: !keepFiles
1185
1471
  });
@@ -1218,7 +1504,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1218
1504
  }
1219
1505
  const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
1220
1506
  const resolved = resolve4(expandHome(normalized));
1221
- const config = loadConfig();
1507
+ const config2 = loadConfig();
1222
1508
  if (existsSync4(resolved)) {
1223
1509
  if (opts.link) {
1224
1510
  const probe = await installPluginFromPath({ path: resolved, dryRun: true });
@@ -1226,7 +1512,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1226
1512
  console.error(probe.error);
1227
1513
  process.exit(1);
1228
1514
  }
1229
- let next3 = addPluginLoadPath(config, resolved);
1515
+ let next3 = addPluginLoadPath(config2, resolved);
1230
1516
  next3 = enablePluginInConfig(next3, probe.pluginId);
1231
1517
  next3 = recordPluginInstall(next3, {
1232
1518
  pluginId: probe.pluginId,
@@ -1251,7 +1537,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1251
1537
  console.error(result2.error);
1252
1538
  process.exit(1);
1253
1539
  }
1254
- let next2 = enablePluginInConfig(config, result2.pluginId);
1540
+ let next2 = enablePluginInConfig(config2, result2.pluginId);
1255
1541
  next2 = recordPluginInstall(next2, {
1256
1542
  pluginId: result2.pluginId,
1257
1543
  source: this.isArchivePath(resolved) ? "archive" : "path",
@@ -1283,7 +1569,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1283
1569
  console.error(result.error);
1284
1570
  process.exit(1);
1285
1571
  }
1286
- let next = enablePluginInConfig(config, result.pluginId);
1572
+ let next = enablePluginInConfig(config2, result.pluginId);
1287
1573
  next = recordPluginInstall(next, {
1288
1574
  pluginId: result.pluginId,
1289
1575
  source: "npm",
@@ -1296,12 +1582,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1296
1582
  console.log("Restart the gateway to load plugins.");
1297
1583
  }
1298
1584
  pluginsDoctor() {
1299
- const config = loadConfig();
1300
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1585
+ const config2 = loadConfig();
1586
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1301
1587
  const report = buildPluginStatusReport({
1302
- config,
1588
+ config: config2,
1303
1589
  workspaceDir,
1304
- reservedChannelIds: Object.keys(config.channels),
1590
+ reservedChannelIds: Object.keys(config2.channels),
1305
1591
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
1306
1592
  });
1307
1593
  const pluginErrors = report.plugins.filter((plugin) => plugin.status === "error");
@@ -1349,20 +1635,20 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1349
1635
  console.log(` Path: ${result.destinationDir}`);
1350
1636
  }
1351
1637
  channelsStatus() {
1352
- const config = loadConfig();
1638
+ const config2 = loadConfig();
1353
1639
  console.log("Channel Status");
1354
- console.log(`WhatsApp: ${config.channels.whatsapp.enabled ? "\u2713" : "\u2717"}`);
1355
- console.log(`Discord: ${config.channels.discord.enabled ? "\u2713" : "\u2717"}`);
1356
- console.log(`Feishu: ${config.channels.feishu.enabled ? "\u2713" : "\u2717"}`);
1357
- console.log(`Mochat: ${config.channels.mochat.enabled ? "\u2713" : "\u2717"}`);
1358
- console.log(`Telegram: ${config.channels.telegram.enabled ? "\u2713" : "\u2717"}`);
1359
- console.log(`Slack: ${config.channels.slack.enabled ? "\u2713" : "\u2717"}`);
1360
- console.log(`QQ: ${config.channels.qq.enabled ? "\u2713" : "\u2717"}`);
1361
- 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);
1362
1648
  const report = buildPluginStatusReport({
1363
- config,
1649
+ config: config2,
1364
1650
  workspaceDir,
1365
- reservedChannelIds: Object.keys(config.channels),
1651
+ reservedChannelIds: Object.keys(config2.channels),
1366
1652
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
1367
1653
  });
1368
1654
  const pluginChannels = report.plugins.filter((plugin) => plugin.status === "loaded" && plugin.channelIds.length > 0);
@@ -1389,9 +1675,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1389
1675
  console.error("--channel is required");
1390
1676
  process.exit(1);
1391
1677
  }
1392
- const config = loadConfig();
1393
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1394
- 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);
1395
1681
  const bindings = getPluginChannelBindings(pluginRegistry);
1396
1682
  const binding = bindings.find((entry) => entry.channelId === channelId || entry.pluginId === channelId);
1397
1683
  if (!binding) {
@@ -1410,7 +1696,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1410
1696
  url: opts.url,
1411
1697
  httpUrl: opts.httpUrl
1412
1698
  };
1413
- const currentView = this.toPluginConfigView(config, bindings);
1699
+ const currentView = this.toPluginConfigView(config2, bindings);
1414
1700
  const accountId = binding.channel.config?.defaultAccountId?.(currentView) ?? "default";
1415
1701
  const validateError = setup.validateInput?.({
1416
1702
  cfg: currentView,
@@ -1430,17 +1716,17 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1430
1716
  console.error("Channel setup returned invalid config payload.");
1431
1717
  process.exit(1);
1432
1718
  }
1433
- let next = this.mergePluginConfigView(config, nextView, bindings);
1719
+ let next = this.mergePluginConfigView(config2, nextView, bindings);
1434
1720
  next = enablePluginInConfig(next, binding.pluginId);
1435
1721
  saveConfig(next);
1436
1722
  console.log(`Configured channel "${binding.channelId}" via plugin "${binding.pluginId}".`);
1437
1723
  console.log("Restart the gateway to apply changes.");
1438
1724
  }
1439
- toPluginConfigView(config, bindings) {
1440
- const view = JSON.parse(JSON.stringify(config));
1725
+ toPluginConfigView(config2, bindings) {
1726
+ const view = JSON.parse(JSON.stringify(config2));
1441
1727
  const channels2 = view.channels && typeof view.channels === "object" && !Array.isArray(view.channels) ? { ...view.channels } : {};
1442
1728
  for (const binding of bindings) {
1443
- const pluginConfig = config.plugins.entries?.[binding.pluginId]?.config;
1729
+ const pluginConfig = config2.plugins.entries?.[binding.pluginId]?.config;
1444
1730
  if (!pluginConfig || typeof pluginConfig !== "object" || Array.isArray(pluginConfig)) {
1445
1731
  continue;
1446
1732
  }
@@ -1544,15 +1830,15 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1544
1830
  }
1545
1831
  status() {
1546
1832
  const configPath = getConfigPath();
1547
- const config = loadConfig();
1548
- const workspace = getWorkspacePath(config.agents.defaults.workspace);
1833
+ const config2 = loadConfig();
1834
+ const workspace = getWorkspacePath(config2.agents.defaults.workspace);
1549
1835
  console.log(`${this.logo} ${APP_NAME} Status
1550
1836
  `);
1551
1837
  console.log(`Config: ${configPath} ${existsSync4(configPath) ? "\u2713" : "\u2717"}`);
1552
1838
  console.log(`Workspace: ${workspace} ${existsSync4(workspace) ? "\u2713" : "\u2717"}`);
1553
- console.log(`Model: ${config.agents.defaults.model}`);
1839
+ console.log(`Model: ${config2.agents.defaults.model}`);
1554
1840
  for (const spec of PROVIDERS) {
1555
- const provider = config.providers[spec.name];
1841
+ const provider = config2.providers[spec.name];
1556
1842
  if (!provider) {
1557
1843
  continue;
1558
1844
  }
@@ -1563,9 +1849,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1563
1849
  }
1564
1850
  }
1565
1851
  }
1566
- loadPluginRegistry(config, workspaceDir) {
1852
+ loadPluginRegistry(config2, workspaceDir) {
1567
1853
  return loadOpenClawPlugins({
1568
- config,
1854
+ config: config2,
1569
1855
  workspaceDir,
1570
1856
  reservedToolNames: [
1571
1857
  "read_file",
@@ -1586,7 +1872,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1586
1872
  "gateway",
1587
1873
  "cron"
1588
1874
  ],
1589
- reservedChannelIds: Object.keys(config.channels),
1875
+ reservedChannelIds: Object.keys(config2.channels),
1590
1876
  reservedProviderIds: PROVIDERS.map((provider) => provider.name),
1591
1877
  logger: {
1592
1878
  info: (message) => console.log(message),
@@ -1630,19 +1916,19 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1630
1916
  }
1631
1917
  }
1632
1918
  async startGateway(options = {}) {
1633
- const config = loadConfig();
1634
- const workspace = getWorkspacePath(config.agents.defaults.workspace);
1635
- 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);
1636
1922
  const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
1637
1923
  this.logPluginDiagnostics(pluginRegistry);
1638
1924
  const bus = new MessageBus();
1639
- 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);
1640
1926
  const providerManager = provider ? new ProviderManager(provider) : null;
1641
1927
  const sessionManager = new SessionManager(workspace);
1642
1928
  const cronStorePath = join3(getDataDir2(), "cron", "jobs.json");
1643
1929
  const cron2 = new CronService(cronStorePath);
1644
1930
  const pluginUiMetadata = getPluginUiMetadataFromRegistry(pluginRegistry);
1645
- const uiConfig = resolveUiConfig(config, options.uiOverrides);
1931
+ const uiConfig = resolveUiConfig(config2, options.uiOverrides);
1646
1932
  const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
1647
1933
  if (!provider) {
1648
1934
  this.startUiIfEnabled(uiConfig, uiStaticDir);
@@ -1651,9 +1937,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1651
1937
  });
1652
1938
  return;
1653
1939
  }
1654
- const channels2 = new ChannelManager(config, bus, sessionManager, extensionRegistry.channels);
1940
+ const channels2 = new ChannelManager(config2, bus, sessionManager, extensionRegistry.channels);
1655
1941
  const reloader = new ConfigReloader({
1656
- initialConfig: config,
1942
+ initialConfig: config2,
1657
1943
  channels: channels2,
1658
1944
  bus,
1659
1945
  sessionManager,
@@ -1676,16 +1962,16 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1676
1962
  bus,
1677
1963
  providerManager: providerManager ?? new ProviderManager(provider),
1678
1964
  workspace,
1679
- model: config.agents.defaults.model,
1680
- maxIterations: config.agents.defaults.maxToolIterations,
1681
- braveApiKey: config.tools.web.search.apiKey || void 0,
1682
- 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,
1683
1969
  cronService: cron2,
1684
- restrictToWorkspace: config.tools.restrictToWorkspace,
1970
+ restrictToWorkspace: config2.tools.restrictToWorkspace,
1685
1971
  sessionManager,
1686
- contextConfig: config.agents.context,
1972
+ contextConfig: config2.agents.context,
1687
1973
  gatewayController,
1688
- config,
1974
+ config: config2,
1689
1975
  extensionRegistry,
1690
1976
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
1691
1977
  registry: pluginRegistry,
@@ -1825,8 +2111,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1825
2111
  }
1826
2112
  }
1827
2113
  async runForeground(options) {
1828
- const config = loadConfig();
1829
- const uiConfig = resolveUiConfig(config, options.uiOverrides);
2114
+ const config2 = loadConfig();
2115
+ const uiConfig = resolveUiConfig(config2, options.uiOverrides);
1830
2116
  const shouldStartFrontend = options.frontend;
1831
2117
  const frontendPort = Number.isFinite(options.frontendPort) ? options.frontendPort : 5173;
1832
2118
  const frontendDir = shouldStartFrontend ? resolveUiFrontendDir() : null;
@@ -1858,8 +2144,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1858
2144
  });
1859
2145
  }
1860
2146
  async startService(options) {
1861
- const config = loadConfig();
1862
- const uiConfig = resolveUiConfig(config, options.uiOverrides);
2147
+ const config2 = loadConfig();
2148
+ const uiConfig = resolveUiConfig(config2, options.uiOverrides);
1863
2149
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
1864
2150
  const apiUrl = `${uiUrl}/api`;
1865
2151
  const staticDir = resolveUiStaticDir();
@@ -1959,9 +2245,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1959
2245
  const normalized = answer.trim().toLowerCase();
1960
2246
  return normalized === "y" || normalized === "yes";
1961
2247
  }
1962
- makeProvider(config, options) {
1963
- const provider = getProvider(config);
1964
- const model = config.agents.defaults.model;
2248
+ makeProvider(config2, options) {
2249
+ const provider = getProvider(config2);
2250
+ const model = config2.agents.defaults.model;
1965
2251
  if (!provider?.apiKey && !model.startsWith("bedrock/")) {
1966
2252
  if (options?.allowMissing) {
1967
2253
  return null;
@@ -1972,10 +2258,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1972
2258
  }
1973
2259
  return new LiteLLMProvider({
1974
2260
  apiKey: provider?.apiKey ?? null,
1975
- apiBase: getApiBase(config),
2261
+ apiBase: getApiBase(config2),
1976
2262
  defaultModel: model,
1977
2263
  extraHeaders: provider?.extraHeaders ?? null,
1978
- providerName: getProviderName(config),
2264
+ providerName: getProviderName(config2),
1979
2265
  wireApi: provider?.wireApi ?? null
1980
2266
  });
1981
2267
  }
@@ -2203,6 +2489,10 @@ plugins.command("disable <id>").description("Disable a plugin in config").action
2203
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));
2204
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));
2205
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));
2206
2496
  var channels = program.command("channels").description("Manage channels");
2207
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));
2208
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.11",
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",
@@ -38,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "chokidar": "^3.6.0",
40
40
  "commander": "^12.1.0",
41
- "@nextclaw/core": "^0.4.9",
41
+ "@nextclaw/core": "^0.4.10",
42
42
  "@nextclaw/server": "^0.3.5",
43
43
  "@nextclaw/openclaw-compat": "^0.1.2"
44
44
  },
@@ -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",