nextclaw 0.4.12 → 0.4.14

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 +378 -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 });
@@ -863,6 +1048,19 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
863
1048
  open: Boolean(opts.open)
864
1049
  });
865
1050
  }
1051
+ async restart(opts) {
1052
+ const state = readServiceState();
1053
+ if (state && isProcessRunning(state.pid)) {
1054
+ console.log(`Restarting ${APP_NAME}...`);
1055
+ await this.stopService();
1056
+ } else if (state) {
1057
+ clearServiceState();
1058
+ console.log("Service state was stale and has been cleaned up.");
1059
+ } else {
1060
+ console.log("No running service found. Starting a new service.");
1061
+ }
1062
+ await this.start(opts);
1063
+ }
866
1064
  async serve(opts) {
867
1065
  const uiOverrides = {
868
1066
  enabled: true,
@@ -904,23 +1102,23 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
904
1102
  await this.stopService();
905
1103
  }
906
1104
  async agent(opts) {
907
- const config = loadConfig();
908
- const workspace = getWorkspacePath(config.agents.defaults.workspace);
909
- const pluginRegistry = this.loadPluginRegistry(config, workspace);
1105
+ const config2 = loadConfig();
1106
+ const workspace = getWorkspacePath(config2.agents.defaults.workspace);
1107
+ const pluginRegistry = this.loadPluginRegistry(config2, workspace);
910
1108
  const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
911
1109
  this.logPluginDiagnostics(pluginRegistry);
912
1110
  const bus = new MessageBus();
913
- const provider = this.makeProvider(config);
1111
+ const provider = this.makeProvider(config2);
914
1112
  const providerManager = new ProviderManager(provider);
915
1113
  const agentLoop = new AgentLoop({
916
1114
  bus,
917
1115
  providerManager,
918
1116
  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,
1117
+ braveApiKey: config2.tools.web.search.apiKey || void 0,
1118
+ execConfig: config2.tools.exec,
1119
+ restrictToWorkspace: config2.tools.restrictToWorkspace,
1120
+ contextConfig: config2.agents.context,
1121
+ config: config2,
924
1122
  extensionRegistry,
925
1123
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
926
1124
  registry: pluginRegistry,
@@ -1006,12 +1204,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1006
1204
  }
1007
1205
  }
1008
1206
  pluginsList(opts = {}) {
1009
- const config = loadConfig();
1010
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1207
+ const config2 = loadConfig();
1208
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1011
1209
  const report = buildPluginStatusReport({
1012
- config,
1210
+ config: config2,
1013
1211
  workspaceDir,
1014
- reservedChannelIds: Object.keys(config.channels),
1212
+ reservedChannelIds: Object.keys(config2.channels),
1015
1213
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
1016
1214
  });
1017
1215
  const list = opts.enabled ? report.plugins.filter((plugin) => plugin.status === "loaded") : report.plugins;
@@ -1063,12 +1261,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1063
1261
  }
1064
1262
  }
1065
1263
  pluginsInfo(id, opts = {}) {
1066
- const config = loadConfig();
1067
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1264
+ const config2 = loadConfig();
1265
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1068
1266
  const report = buildPluginStatusReport({
1069
- config,
1267
+ config: config2,
1070
1268
  workspaceDir,
1071
- reservedChannelIds: Object.keys(config.channels),
1269
+ reservedChannelIds: Object.keys(config2.channels),
1072
1270
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
1073
1271
  });
1074
1272
  const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
@@ -1080,7 +1278,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1080
1278
  console.log(JSON.stringify(plugin, null, 2));
1081
1279
  return;
1082
1280
  }
1083
- const install = config.plugins.installs?.[plugin.id];
1281
+ const install = config2.plugins.installs?.[plugin.id];
1084
1282
  const lines = [];
1085
1283
  lines.push(plugin.name || plugin.id);
1086
1284
  if (plugin.name && plugin.name !== plugin.id) {
@@ -1129,25 +1327,98 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1129
1327
  }
1130
1328
  console.log(lines.join("\n"));
1131
1329
  }
1330
+ configGet(pathExpr, opts = {}) {
1331
+ const config2 = loadConfig();
1332
+ let parsedPath;
1333
+ try {
1334
+ parsedPath = parseRequiredConfigPath(pathExpr);
1335
+ } catch (error) {
1336
+ console.error(String(error));
1337
+ process.exit(1);
1338
+ return;
1339
+ }
1340
+ const result = getAtConfigPath(config2, parsedPath);
1341
+ if (!result.found) {
1342
+ console.error(`Config path not found: ${pathExpr}`);
1343
+ process.exit(1);
1344
+ return;
1345
+ }
1346
+ if (opts.json) {
1347
+ console.log(JSON.stringify(result.value ?? null, null, 2));
1348
+ return;
1349
+ }
1350
+ if (typeof result.value === "string" || typeof result.value === "number" || typeof result.value === "boolean") {
1351
+ console.log(String(result.value));
1352
+ return;
1353
+ }
1354
+ console.log(JSON.stringify(result.value ?? null, null, 2));
1355
+ }
1356
+ configSet(pathExpr, value, opts = {}) {
1357
+ let parsedPath;
1358
+ try {
1359
+ parsedPath = parseRequiredConfigPath(pathExpr);
1360
+ } catch (error) {
1361
+ console.error(String(error));
1362
+ process.exit(1);
1363
+ return;
1364
+ }
1365
+ let parsedValue;
1366
+ try {
1367
+ parsedValue = parseConfigSetValue(value, opts);
1368
+ } catch (error) {
1369
+ console.error(`Failed to parse config value: ${String(error)}`);
1370
+ process.exit(1);
1371
+ return;
1372
+ }
1373
+ const config2 = loadConfig();
1374
+ try {
1375
+ setAtConfigPath(config2, parsedPath, parsedValue);
1376
+ } catch (error) {
1377
+ console.error(String(error));
1378
+ process.exit(1);
1379
+ return;
1380
+ }
1381
+ saveConfig(config2);
1382
+ console.log(`Updated ${pathExpr}. Restart the gateway to apply.`);
1383
+ }
1384
+ configUnset(pathExpr) {
1385
+ let parsedPath;
1386
+ try {
1387
+ parsedPath = parseRequiredConfigPath(pathExpr);
1388
+ } catch (error) {
1389
+ console.error(String(error));
1390
+ process.exit(1);
1391
+ return;
1392
+ }
1393
+ const config2 = loadConfig();
1394
+ const removed = unsetAtConfigPath(config2, parsedPath);
1395
+ if (!removed) {
1396
+ console.error(`Config path not found: ${pathExpr}`);
1397
+ process.exit(1);
1398
+ return;
1399
+ }
1400
+ saveConfig(config2);
1401
+ console.log(`Removed ${pathExpr}. Restart the gateway to apply.`);
1402
+ }
1132
1403
  pluginsEnable(id) {
1133
- const config = loadConfig();
1134
- const next = enablePluginInConfig(config, id);
1404
+ const config2 = loadConfig();
1405
+ const next = enablePluginInConfig(config2, id);
1135
1406
  saveConfig(next);
1136
1407
  console.log(`Enabled plugin "${id}". Restart the gateway to apply.`);
1137
1408
  }
1138
1409
  pluginsDisable(id) {
1139
- const config = loadConfig();
1140
- const next = disablePluginInConfig(config, id);
1410
+ const config2 = loadConfig();
1411
+ const next = disablePluginInConfig(config2, id);
1141
1412
  saveConfig(next);
1142
1413
  console.log(`Disabled plugin "${id}". Restart the gateway to apply.`);
1143
1414
  }
1144
1415
  async pluginsUninstall(id, opts = {}) {
1145
- const config = loadConfig();
1146
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1416
+ const config2 = loadConfig();
1417
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1147
1418
  const report = buildPluginStatusReport({
1148
- config,
1419
+ config: config2,
1149
1420
  workspaceDir,
1150
- reservedChannelIds: Object.keys(config.channels),
1421
+ reservedChannelIds: Object.keys(config2.channels),
1151
1422
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
1152
1423
  });
1153
1424
  const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
@@ -1156,8 +1427,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1156
1427
  }
1157
1428
  const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
1158
1429
  const pluginId = plugin?.id ?? id;
1159
- const hasEntry = pluginId in (config.plugins.entries ?? {});
1160
- const hasInstall = pluginId in (config.plugins.installs ?? {});
1430
+ const hasEntry = pluginId in (config2.plugins.entries ?? {});
1431
+ const hasInstall = pluginId in (config2.plugins.installs ?? {});
1161
1432
  if (!hasEntry && !hasInstall) {
1162
1433
  if (plugin) {
1163
1434
  console.error(
@@ -1168,8 +1439,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1168
1439
  }
1169
1440
  process.exit(1);
1170
1441
  }
1171
- const install = config.plugins.installs?.[pluginId];
1172
- const isLinked = install?.source === "path";
1442
+ const install = config2.plugins.installs?.[pluginId];
1443
+ const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve4(install.installPath) === resolve4(install.sourcePath));
1173
1444
  const preview = [];
1174
1445
  if (hasEntry) {
1175
1446
  preview.push("config entry");
@@ -1177,10 +1448,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1177
1448
  if (hasInstall) {
1178
1449
  preview.push("install record");
1179
1450
  }
1180
- if (config.plugins.allow?.includes(pluginId)) {
1451
+ if (config2.plugins.allow?.includes(pluginId)) {
1181
1452
  preview.push("allowlist entry");
1182
1453
  }
1183
- if (isLinked && install?.sourcePath && config.plugins.load?.paths?.includes(install.sourcePath)) {
1454
+ if (isLinked && install?.sourcePath && config2.plugins.load?.paths?.includes(install.sourcePath)) {
1184
1455
  preview.push("load path");
1185
1456
  }
1186
1457
  const deleteTarget = !keepFiles ? resolveUninstallDirectoryTarget({
@@ -1207,7 +1478,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1207
1478
  }
1208
1479
  }
1209
1480
  const result = await uninstallPlugin({
1210
- config,
1481
+ config: config2,
1211
1482
  pluginId,
1212
1483
  deleteFiles: !keepFiles
1213
1484
  });
@@ -1246,7 +1517,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1246
1517
  }
1247
1518
  const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
1248
1519
  const resolved = resolve4(expandHome(normalized));
1249
- const config = loadConfig();
1520
+ const config2 = loadConfig();
1250
1521
  if (existsSync4(resolved)) {
1251
1522
  if (opts.link) {
1252
1523
  const probe = await installPluginFromPath({ path: resolved, dryRun: true });
@@ -1254,7 +1525,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1254
1525
  console.error(probe.error);
1255
1526
  process.exit(1);
1256
1527
  }
1257
- let next3 = addPluginLoadPath(config, resolved);
1528
+ let next3 = addPluginLoadPath(config2, resolved);
1258
1529
  next3 = enablePluginInConfig(next3, probe.pluginId);
1259
1530
  next3 = recordPluginInstall(next3, {
1260
1531
  pluginId: probe.pluginId,
@@ -1279,7 +1550,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1279
1550
  console.error(result2.error);
1280
1551
  process.exit(1);
1281
1552
  }
1282
- let next2 = enablePluginInConfig(config, result2.pluginId);
1553
+ let next2 = enablePluginInConfig(config2, result2.pluginId);
1283
1554
  next2 = recordPluginInstall(next2, {
1284
1555
  pluginId: result2.pluginId,
1285
1556
  source: this.isArchivePath(resolved) ? "archive" : "path",
@@ -1311,7 +1582,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1311
1582
  console.error(result.error);
1312
1583
  process.exit(1);
1313
1584
  }
1314
- let next = enablePluginInConfig(config, result.pluginId);
1585
+ let next = enablePluginInConfig(config2, result.pluginId);
1315
1586
  next = recordPluginInstall(next, {
1316
1587
  pluginId: result.pluginId,
1317
1588
  source: "npm",
@@ -1324,12 +1595,12 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1324
1595
  console.log("Restart the gateway to load plugins.");
1325
1596
  }
1326
1597
  pluginsDoctor() {
1327
- const config = loadConfig();
1328
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1598
+ const config2 = loadConfig();
1599
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1329
1600
  const report = buildPluginStatusReport({
1330
- config,
1601
+ config: config2,
1331
1602
  workspaceDir,
1332
- reservedChannelIds: Object.keys(config.channels),
1603
+ reservedChannelIds: Object.keys(config2.channels),
1333
1604
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
1334
1605
  });
1335
1606
  const pluginErrors = report.plugins.filter((plugin) => plugin.status === "error");
@@ -1377,20 +1648,20 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1377
1648
  console.log(` Path: ${result.destinationDir}`);
1378
1649
  }
1379
1650
  channelsStatus() {
1380
- const config = loadConfig();
1651
+ const config2 = loadConfig();
1381
1652
  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);
1653
+ console.log(`WhatsApp: ${config2.channels.whatsapp.enabled ? "\u2713" : "\u2717"}`);
1654
+ console.log(`Discord: ${config2.channels.discord.enabled ? "\u2713" : "\u2717"}`);
1655
+ console.log(`Feishu: ${config2.channels.feishu.enabled ? "\u2713" : "\u2717"}`);
1656
+ console.log(`Mochat: ${config2.channels.mochat.enabled ? "\u2713" : "\u2717"}`);
1657
+ console.log(`Telegram: ${config2.channels.telegram.enabled ? "\u2713" : "\u2717"}`);
1658
+ console.log(`Slack: ${config2.channels.slack.enabled ? "\u2713" : "\u2717"}`);
1659
+ console.log(`QQ: ${config2.channels.qq.enabled ? "\u2713" : "\u2717"}`);
1660
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1390
1661
  const report = buildPluginStatusReport({
1391
- config,
1662
+ config: config2,
1392
1663
  workspaceDir,
1393
- reservedChannelIds: Object.keys(config.channels),
1664
+ reservedChannelIds: Object.keys(config2.channels),
1394
1665
  reservedProviderIds: PROVIDERS.map((provider) => provider.name)
1395
1666
  });
1396
1667
  const pluginChannels = report.plugins.filter((plugin) => plugin.status === "loaded" && plugin.channelIds.length > 0);
@@ -1417,9 +1688,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1417
1688
  console.error("--channel is required");
1418
1689
  process.exit(1);
1419
1690
  }
1420
- const config = loadConfig();
1421
- const workspaceDir = getWorkspacePath(config.agents.defaults.workspace);
1422
- const pluginRegistry = this.loadPluginRegistry(config, workspaceDir);
1691
+ const config2 = loadConfig();
1692
+ const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
1693
+ const pluginRegistry = this.loadPluginRegistry(config2, workspaceDir);
1423
1694
  const bindings = getPluginChannelBindings(pluginRegistry);
1424
1695
  const binding = bindings.find((entry) => entry.channelId === channelId || entry.pluginId === channelId);
1425
1696
  if (!binding) {
@@ -1438,7 +1709,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1438
1709
  url: opts.url,
1439
1710
  httpUrl: opts.httpUrl
1440
1711
  };
1441
- const currentView = this.toPluginConfigView(config, bindings);
1712
+ const currentView = this.toPluginConfigView(config2, bindings);
1442
1713
  const accountId = binding.channel.config?.defaultAccountId?.(currentView) ?? "default";
1443
1714
  const validateError = setup.validateInput?.({
1444
1715
  cfg: currentView,
@@ -1458,17 +1729,17 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1458
1729
  console.error("Channel setup returned invalid config payload.");
1459
1730
  process.exit(1);
1460
1731
  }
1461
- let next = this.mergePluginConfigView(config, nextView, bindings);
1732
+ let next = this.mergePluginConfigView(config2, nextView, bindings);
1462
1733
  next = enablePluginInConfig(next, binding.pluginId);
1463
1734
  saveConfig(next);
1464
1735
  console.log(`Configured channel "${binding.channelId}" via plugin "${binding.pluginId}".`);
1465
1736
  console.log("Restart the gateway to apply changes.");
1466
1737
  }
1467
- toPluginConfigView(config, bindings) {
1468
- const view = JSON.parse(JSON.stringify(config));
1738
+ toPluginConfigView(config2, bindings) {
1739
+ const view = JSON.parse(JSON.stringify(config2));
1469
1740
  const channels2 = view.channels && typeof view.channels === "object" && !Array.isArray(view.channels) ? { ...view.channels } : {};
1470
1741
  for (const binding of bindings) {
1471
- const pluginConfig = config.plugins.entries?.[binding.pluginId]?.config;
1742
+ const pluginConfig = config2.plugins.entries?.[binding.pluginId]?.config;
1472
1743
  if (!pluginConfig || typeof pluginConfig !== "object" || Array.isArray(pluginConfig)) {
1473
1744
  continue;
1474
1745
  }
@@ -1572,15 +1843,15 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1572
1843
  }
1573
1844
  status() {
1574
1845
  const configPath = getConfigPath();
1575
- const config = loadConfig();
1576
- const workspace = getWorkspacePath(config.agents.defaults.workspace);
1846
+ const config2 = loadConfig();
1847
+ const workspace = getWorkspacePath(config2.agents.defaults.workspace);
1577
1848
  console.log(`${this.logo} ${APP_NAME} Status
1578
1849
  `);
1579
1850
  console.log(`Config: ${configPath} ${existsSync4(configPath) ? "\u2713" : "\u2717"}`);
1580
1851
  console.log(`Workspace: ${workspace} ${existsSync4(workspace) ? "\u2713" : "\u2717"}`);
1581
- console.log(`Model: ${config.agents.defaults.model}`);
1852
+ console.log(`Model: ${config2.agents.defaults.model}`);
1582
1853
  for (const spec of PROVIDERS) {
1583
- const provider = config.providers[spec.name];
1854
+ const provider = config2.providers[spec.name];
1584
1855
  if (!provider) {
1585
1856
  continue;
1586
1857
  }
@@ -1591,9 +1862,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1591
1862
  }
1592
1863
  }
1593
1864
  }
1594
- loadPluginRegistry(config, workspaceDir) {
1865
+ loadPluginRegistry(config2, workspaceDir) {
1595
1866
  return loadOpenClawPlugins({
1596
- config,
1867
+ config: config2,
1597
1868
  workspaceDir,
1598
1869
  reservedToolNames: [
1599
1870
  "read_file",
@@ -1614,7 +1885,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1614
1885
  "gateway",
1615
1886
  "cron"
1616
1887
  ],
1617
- reservedChannelIds: Object.keys(config.channels),
1888
+ reservedChannelIds: Object.keys(config2.channels),
1618
1889
  reservedProviderIds: PROVIDERS.map((provider) => provider.name),
1619
1890
  logger: {
1620
1891
  info: (message) => console.log(message),
@@ -1658,19 +1929,19 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1658
1929
  }
1659
1930
  }
1660
1931
  async startGateway(options = {}) {
1661
- const config = loadConfig();
1662
- const workspace = getWorkspacePath(config.agents.defaults.workspace);
1663
- const pluginRegistry = this.loadPluginRegistry(config, workspace);
1932
+ const config2 = loadConfig();
1933
+ const workspace = getWorkspacePath(config2.agents.defaults.workspace);
1934
+ const pluginRegistry = this.loadPluginRegistry(config2, workspace);
1664
1935
  const extensionRegistry = this.toExtensionRegistry(pluginRegistry);
1665
1936
  this.logPluginDiagnostics(pluginRegistry);
1666
1937
  const bus = new MessageBus();
1667
- const provider = options.allowMissingProvider === true ? this.makeProvider(config, { allowMissing: true }) : this.makeProvider(config);
1938
+ const provider = options.allowMissingProvider === true ? this.makeProvider(config2, { allowMissing: true }) : this.makeProvider(config2);
1668
1939
  const providerManager = provider ? new ProviderManager(provider) : null;
1669
1940
  const sessionManager = new SessionManager(workspace);
1670
1941
  const cronStorePath = join3(getDataDir2(), "cron", "jobs.json");
1671
1942
  const cron2 = new CronService(cronStorePath);
1672
1943
  const pluginUiMetadata = getPluginUiMetadataFromRegistry(pluginRegistry);
1673
- const uiConfig = resolveUiConfig(config, options.uiOverrides);
1944
+ const uiConfig = resolveUiConfig(config2, options.uiOverrides);
1674
1945
  const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
1675
1946
  if (!provider) {
1676
1947
  this.startUiIfEnabled(uiConfig, uiStaticDir);
@@ -1679,9 +1950,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1679
1950
  });
1680
1951
  return;
1681
1952
  }
1682
- const channels2 = new ChannelManager(config, bus, sessionManager, extensionRegistry.channels);
1953
+ const channels2 = new ChannelManager(config2, bus, sessionManager, extensionRegistry.channels);
1683
1954
  const reloader = new ConfigReloader({
1684
- initialConfig: config,
1955
+ initialConfig: config2,
1685
1956
  channels: channels2,
1686
1957
  bus,
1687
1958
  sessionManager,
@@ -1704,16 +1975,16 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1704
1975
  bus,
1705
1976
  providerManager: providerManager ?? new ProviderManager(provider),
1706
1977
  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,
1978
+ model: config2.agents.defaults.model,
1979
+ maxIterations: config2.agents.defaults.maxToolIterations,
1980
+ braveApiKey: config2.tools.web.search.apiKey || void 0,
1981
+ execConfig: config2.tools.exec,
1711
1982
  cronService: cron2,
1712
- restrictToWorkspace: config.tools.restrictToWorkspace,
1983
+ restrictToWorkspace: config2.tools.restrictToWorkspace,
1713
1984
  sessionManager,
1714
- contextConfig: config.agents.context,
1985
+ contextConfig: config2.agents.context,
1715
1986
  gatewayController,
1716
- config,
1987
+ config: config2,
1717
1988
  extensionRegistry,
1718
1989
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
1719
1990
  registry: pluginRegistry,
@@ -1853,8 +2124,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1853
2124
  }
1854
2125
  }
1855
2126
  async runForeground(options) {
1856
- const config = loadConfig();
1857
- const uiConfig = resolveUiConfig(config, options.uiOverrides);
2127
+ const config2 = loadConfig();
2128
+ const uiConfig = resolveUiConfig(config2, options.uiOverrides);
1858
2129
  const shouldStartFrontend = options.frontend;
1859
2130
  const frontendPort = Number.isFinite(options.frontendPort) ? options.frontendPort : 5173;
1860
2131
  const frontendDir = shouldStartFrontend ? resolveUiFrontendDir() : null;
@@ -1886,8 +2157,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1886
2157
  });
1887
2158
  }
1888
2159
  async startService(options) {
1889
- const config = loadConfig();
1890
- const uiConfig = resolveUiConfig(config, options.uiOverrides);
2160
+ const config2 = loadConfig();
2161
+ const uiConfig = resolveUiConfig(config2, options.uiOverrides);
1891
2162
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
1892
2163
  const apiUrl = `${uiUrl}/api`;
1893
2164
  const staticDir = resolveUiStaticDir();
@@ -1987,9 +2258,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
1987
2258
  const normalized = answer.trim().toLowerCase();
1988
2259
  return normalized === "y" || normalized === "yes";
1989
2260
  }
1990
- makeProvider(config, options) {
1991
- const provider = getProvider(config);
1992
- const model = config.agents.defaults.model;
2261
+ makeProvider(config2, options) {
2262
+ const provider = getProvider(config2);
2263
+ const model = config2.agents.defaults.model;
1993
2264
  if (!provider?.apiKey && !model.startsWith("bedrock/")) {
1994
2265
  if (options?.allowMissing) {
1995
2266
  return null;
@@ -2000,10 +2271,10 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
2000
2271
  }
2001
2272
  return new LiteLLMProvider({
2002
2273
  apiKey: provider?.apiKey ?? null,
2003
- apiBase: getApiBase(config),
2274
+ apiBase: getApiBase(config2),
2004
2275
  defaultModel: model,
2005
2276
  extraHeaders: provider?.extraHeaders ?? null,
2006
- providerName: getProviderName(config),
2277
+ providerName: getProviderName(config2),
2007
2278
  wireApi: provider?.wireApi ?? null
2008
2279
  });
2009
2280
  }
@@ -2212,6 +2483,7 @@ program.command("init").description(`Initialize ${APP_NAME2} configuration and w
2212
2483
  program.command("gateway").description(`Start the ${APP_NAME2} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
2213
2484
  program.command("ui").description(`Start the ${APP_NAME2} UI with gateway`).option("--host <host>", "UI host").option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
2214
2485
  program.command("start").description(`Start the ${APP_NAME2} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => runtime.start(opts));
2486
+ program.command("restart").description(`Restart the ${APP_NAME2} background service`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after restart", false).action(async (opts) => runtime.restart(opts));
2215
2487
  program.command("serve").description(`Run the ${APP_NAME2} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => runtime.serve(opts));
2216
2488
  program.command("stop").description(`Stop the ${APP_NAME2} background service`).action(async () => runtime.stop());
2217
2489
  program.command("agent").description("Interact with the agent directly").option("-m, --message <message>", "Message to send to the agent").option("-s, --session <session>", "Session ID", "cli:default").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => runtime.agent(opts));
@@ -2231,6 +2503,10 @@ plugins.command("disable <id>").description("Disable a plugin in config").action
2231
2503
  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
2504
  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
2505
  plugins.command("doctor").description("Report plugin load issues").action(() => runtime.pluginsDoctor());
2506
+ var config = program.command("config").description("Manage config values");
2507
+ config.command("get <path>").description("Get a config value by dot path").option("--json", "Output JSON", false).action((path, opts) => runtime.configGet(path, opts));
2508
+ 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));
2509
+ config.command("unset <path>").description("Remove a config value by dot path").action((path) => runtime.configUnset(path));
2234
2510
  var channels = program.command("channels").description("Manage channels");
2235
2511
  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
2512
  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.14",
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",