nextclaw 0.8.33 → 0.8.35

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 (32) hide show
  1. package/dist/cli/index.js +416 -57
  2. package/package.json +3 -3
  3. package/ui-dist/assets/ChannelsList-AKnD2r1L.js +1 -0
  4. package/ui-dist/assets/ChatPage-DidO_pAN.js +32 -0
  5. package/ui-dist/assets/{CronConfig-9dYfTRJl.js → CronConfig-C1pm-oKA.js} +1 -1
  6. package/ui-dist/assets/{DocBrowser-BIV0vpA0.js → DocBrowser-BY90Lf6L.js} +1 -1
  7. package/ui-dist/assets/{MarketplacePage-2Zi0JSVi.js → MarketplacePage-BRtmhP3G.js} +1 -1
  8. package/ui-dist/assets/{ModelConfig-h21P5rV0.js → ModelConfig-Dga1Ko7_.js} +1 -1
  9. package/ui-dist/assets/{ProvidersList-DEaK1a3y.js → ProvidersList-DvCoBTrT.js} +1 -1
  10. package/ui-dist/assets/{RuntimeConfig-DXMzf-gF.js → RuntimeConfig-2aqBJ6Xn.js} +1 -1
  11. package/ui-dist/assets/SecretsConfig-wlnh__z0.js +3 -0
  12. package/ui-dist/assets/{SessionsConfig-SdXvn_9E.js → SessionsConfig-CN2WymbH.js} +2 -2
  13. package/ui-dist/assets/{action-link-C9xMkxl2.js → action-link-DLZDwUfD.js} +1 -1
  14. package/ui-dist/assets/{card-Cnqfntk5.js → card-D3dD-I5t.js} +1 -1
  15. package/ui-dist/assets/chat-message-DZV2Z5oc.js +5 -0
  16. package/ui-dist/assets/{dialog-DJs630RE.js → dialog-DZ0VC-RD.js} +1 -1
  17. package/ui-dist/assets/index-BsDasSXm.css +1 -0
  18. package/ui-dist/assets/index-D_vv0E-O.js +2 -0
  19. package/ui-dist/assets/{label-CXGuE6Oa.js → label-CJIvvG6o.js} +1 -1
  20. package/ui-dist/assets/{page-layout-BVZlyPFt.js → page-layout-CLgr0qym.js} +1 -1
  21. package/ui-dist/assets/{switch-BLF45eI3.js → switch-C-0Q8OH2.js} +1 -1
  22. package/ui-dist/assets/{tabs-custom-DQ0GpEV5.js → tabs-custom-D4Gs3BGM.js} +1 -1
  23. package/ui-dist/assets/useConfig-R5uGhZtD.js +1 -0
  24. package/ui-dist/assets/{useConfirmDialog-CK7KAyDf.js → useConfirmDialog-AMeSTA83.js} +1 -1
  25. package/ui-dist/assets/{vendor-RXIbhDBC.js → vendor-H2M3a_4Z.js} +1 -1
  26. package/ui-dist/index.html +3 -3
  27. package/ui-dist/assets/ChannelsList-TFFw4Cem.js +0 -1
  28. package/ui-dist/assets/ChatPage-BUm3UPap.js +0 -32
  29. package/ui-dist/assets/chat-message-B7oqvJ2d.js +0 -3
  30. package/ui-dist/assets/index-CrUDzcei.js +0 -2
  31. package/ui-dist/assets/index-Zy7fAOe1.css +0 -1
  32. package/ui-dist/assets/useConfig-vFQvF4kn.js +0 -1
package/dist/cli/index.js CHANGED
@@ -6,9 +6,9 @@ import { APP_NAME as APP_NAME5, APP_TAGLINE } from "@nextclaw/core";
6
6
 
7
7
  // src/cli/runtime.ts
8
8
  import {
9
- loadConfig as loadConfig6,
10
- saveConfig as saveConfig5,
11
- getConfigPath as getConfigPath3,
9
+ loadConfig as loadConfig7,
10
+ saveConfig as saveConfig6,
11
+ getConfigPath as getConfigPath4,
12
12
  getDataDir as getDataDir7,
13
13
  ConfigSchema as ConfigSchema2,
14
14
  getWorkspacePath as getWorkspacePath6,
@@ -16,6 +16,7 @@ import {
16
16
  MessageBus as MessageBus2,
17
17
  AgentLoop as AgentLoop2,
18
18
  ProviderManager as ProviderManager2,
19
+ resolveConfigSecrets as resolveConfigSecrets3,
19
20
  APP_NAME as APP_NAME4,
20
21
  DEFAULT_WORKSPACE_DIR,
21
22
  DEFAULT_WORKSPACE_PATH
@@ -25,7 +26,7 @@ import {
25
26
  resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2,
26
27
  setPluginRuntimeBridge as setPluginRuntimeBridge2
27
28
  } from "@nextclaw/openclaw-compat";
28
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
29
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
29
30
  import { join as join6, resolve as resolve9 } from "path";
30
31
  import { createInterface as createInterface2 } from "readline";
31
32
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -1312,9 +1313,332 @@ var ConfigCommands = class {
1312
1313
  }
1313
1314
  };
1314
1315
 
1316
+ // src/cli/commands/secrets.ts
1317
+ import { readFileSync as readFileSync3 } from "fs";
1318
+ import {
1319
+ buildReloadPlan as buildReloadPlan2,
1320
+ diffConfigPaths as diffConfigPaths2,
1321
+ getConfigPath,
1322
+ loadConfig as loadConfig3,
1323
+ resolveConfigSecrets,
1324
+ saveConfig as saveConfig3
1325
+ } from "@nextclaw/core";
1326
+ var SECRET_SOURCES = ["env", "file", "exec"];
1327
+ function normalizeSecretSource(value) {
1328
+ if (typeof value !== "string") {
1329
+ return null;
1330
+ }
1331
+ const normalized = value.trim().toLowerCase();
1332
+ return SECRET_SOURCES.includes(normalized) ? normalized : null;
1333
+ }
1334
+ function normalizeOptionalString(value) {
1335
+ if (typeof value !== "string") {
1336
+ return void 0;
1337
+ }
1338
+ const trimmed = value.trim();
1339
+ return trimmed || void 0;
1340
+ }
1341
+ function parseTimeoutMs(value) {
1342
+ if (value === void 0 || value === null || value === "") {
1343
+ return void 0;
1344
+ }
1345
+ const parsed = Number(value);
1346
+ if (!Number.isFinite(parsed) || parsed <= 0) {
1347
+ throw new Error(`invalid timeout: ${String(value)}`);
1348
+ }
1349
+ return Math.trunc(parsed);
1350
+ }
1351
+ function inferProviderAlias(config2, ref) {
1352
+ const explicit = normalizeOptionalString(ref.provider);
1353
+ if (explicit) {
1354
+ return explicit;
1355
+ }
1356
+ const defaultAlias = normalizeOptionalString(config2.secrets.defaults[ref.source]);
1357
+ if (defaultAlias) {
1358
+ return defaultAlias;
1359
+ }
1360
+ return ref.source;
1361
+ }
1362
+ function summarizeAudit(items) {
1363
+ const ok = items.filter((item) => item.ok).length;
1364
+ return {
1365
+ total: items.length,
1366
+ ok,
1367
+ failed: items.length - ok
1368
+ };
1369
+ }
1370
+ function parseRefsPatch(raw) {
1371
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
1372
+ throw new Error("refs patch must be an object");
1373
+ }
1374
+ const output = {};
1375
+ for (const [path, value] of Object.entries(raw)) {
1376
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1377
+ throw new Error(`invalid ref for ${path}`);
1378
+ }
1379
+ const source = normalizeSecretSource(value.source);
1380
+ const id = normalizeOptionalString(value.id);
1381
+ const provider = normalizeOptionalString(value.provider);
1382
+ if (!source || !id) {
1383
+ throw new Error(`invalid ref for ${path}: source/id is required`);
1384
+ }
1385
+ output[path] = { source, ...provider ? { provider } : {}, id };
1386
+ }
1387
+ return output;
1388
+ }
1389
+ function parseApplyFile(raw) {
1390
+ const data = JSON.parse(raw);
1391
+ if (!data || typeof data !== "object" || Array.isArray(data)) {
1392
+ throw new Error("apply file must be an object");
1393
+ }
1394
+ const record = data;
1395
+ if (record.refs || record.providers || record.defaults || Object.prototype.hasOwnProperty.call(record, "enabled")) {
1396
+ const patch = {};
1397
+ if (Object.prototype.hasOwnProperty.call(record, "enabled")) {
1398
+ patch.enabled = Boolean(record.enabled);
1399
+ }
1400
+ if (record.defaults && typeof record.defaults === "object" && !Array.isArray(record.defaults)) {
1401
+ patch.defaults = record.defaults;
1402
+ }
1403
+ if (record.providers && typeof record.providers === "object" && !Array.isArray(record.providers)) {
1404
+ patch.providers = record.providers;
1405
+ }
1406
+ if (record.refs) {
1407
+ patch.refs = parseRefsPatch(record.refs);
1408
+ }
1409
+ return patch;
1410
+ }
1411
+ return { refs: parseRefsPatch(record) };
1412
+ }
1413
+ var SecretsCommands = class {
1414
+ constructor(deps) {
1415
+ this.deps = deps;
1416
+ }
1417
+ secretsAudit(opts = {}) {
1418
+ const config2 = loadConfig3();
1419
+ const configPath = getConfigPath();
1420
+ const refs = config2.secrets.refs;
1421
+ const items = [];
1422
+ for (const [path, ref] of Object.entries(refs)) {
1423
+ const provider = inferProviderAlias(config2, ref);
1424
+ const scopedConfig = structuredClone(config2);
1425
+ scopedConfig.secrets.refs = { [path]: ref };
1426
+ try {
1427
+ const resolved = resolveConfigSecrets(scopedConfig, { configPath });
1428
+ const parsedPath = parseRequiredConfigPath(path);
1429
+ const target = getAtConfigPath(resolved, parsedPath);
1430
+ if (!target.found) {
1431
+ items.push({
1432
+ path,
1433
+ source: ref.source,
1434
+ provider,
1435
+ id: ref.id,
1436
+ ok: false,
1437
+ detail: "resolved but path not found"
1438
+ });
1439
+ continue;
1440
+ }
1441
+ const resolvedValue = target.value;
1442
+ const detail = typeof resolvedValue === "string" ? `resolved (length=${resolvedValue.length})` : `resolved (${typeof resolvedValue})`;
1443
+ items.push({
1444
+ path,
1445
+ source: ref.source,
1446
+ provider,
1447
+ id: ref.id,
1448
+ ok: true,
1449
+ detail
1450
+ });
1451
+ } catch (error) {
1452
+ items.push({
1453
+ path,
1454
+ source: ref.source,
1455
+ provider,
1456
+ id: ref.id,
1457
+ ok: false,
1458
+ detail: String(error)
1459
+ });
1460
+ }
1461
+ }
1462
+ const summary = summarizeAudit(items);
1463
+ if (opts.json) {
1464
+ console.log(JSON.stringify({ summary, items }, null, 2));
1465
+ } else if (!items.length) {
1466
+ console.log("No secret refs configured.");
1467
+ } else {
1468
+ for (const item of items) {
1469
+ const status = item.ok ? "OK" : "ERROR";
1470
+ console.log(
1471
+ `[${status}] ${item.path} <- ${item.source}:${item.provider}:${item.id} (${item.detail})`
1472
+ );
1473
+ }
1474
+ console.log(`Summary: total=${summary.total}, ok=${summary.ok}, failed=${summary.failed}`);
1475
+ }
1476
+ const strict = Boolean(opts.strict);
1477
+ if (strict && summary.failed > 0) {
1478
+ process.exitCode = 1;
1479
+ }
1480
+ }
1481
+ async secretsConfigure(opts) {
1482
+ const alias = normalizeOptionalString(opts.provider);
1483
+ if (!alias) {
1484
+ throw new Error("provider alias is required");
1485
+ }
1486
+ const prevConfig = loadConfig3();
1487
+ const nextConfig = structuredClone(prevConfig);
1488
+ const remove = Boolean(opts.remove);
1489
+ if (remove) {
1490
+ delete nextConfig.secrets.providers[alias];
1491
+ for (const source of SECRET_SOURCES) {
1492
+ if (nextConfig.secrets.defaults[source] === alias) {
1493
+ delete nextConfig.secrets.defaults[source];
1494
+ }
1495
+ }
1496
+ } else {
1497
+ const source = normalizeSecretSource(opts.source);
1498
+ if (!source) {
1499
+ throw new Error("source is required and must be one of env/file/exec");
1500
+ }
1501
+ if (source === "env") {
1502
+ nextConfig.secrets.providers[alias] = {
1503
+ source,
1504
+ ...normalizeOptionalString(opts.prefix) ? { prefix: normalizeOptionalString(opts.prefix) } : {}
1505
+ };
1506
+ } else if (source === "file") {
1507
+ const path = normalizeOptionalString(opts.path);
1508
+ if (!path) {
1509
+ throw new Error("file source requires --path");
1510
+ }
1511
+ nextConfig.secrets.providers[alias] = {
1512
+ source,
1513
+ path,
1514
+ format: "json"
1515
+ };
1516
+ } else {
1517
+ const command = normalizeOptionalString(opts.command);
1518
+ if (!command) {
1519
+ throw new Error("exec source requires --command");
1520
+ }
1521
+ nextConfig.secrets.providers[alias] = {
1522
+ source,
1523
+ command,
1524
+ args: Array.isArray(opts.arg) ? opts.arg : [],
1525
+ ...normalizeOptionalString(opts.cwd) ? { cwd: normalizeOptionalString(opts.cwd) } : {},
1526
+ timeoutMs: parseTimeoutMs(opts.timeoutMs) ?? 5e3
1527
+ };
1528
+ }
1529
+ if (opts.setDefault) {
1530
+ nextConfig.secrets.defaults[source] = alias;
1531
+ }
1532
+ }
1533
+ resolveConfigSecrets(nextConfig, { configPath: getConfigPath() });
1534
+ saveConfig3(nextConfig);
1535
+ await this.requestRestartForConfigDiff({
1536
+ prevConfig,
1537
+ nextConfig,
1538
+ reason: `secrets.configure ${alias}`,
1539
+ manualMessage: "Secrets provider updated. Restart the gateway if required."
1540
+ });
1541
+ if (opts.json) {
1542
+ console.log(JSON.stringify({ ok: true, providers: nextConfig.secrets.providers, defaults: nextConfig.secrets.defaults }, null, 2));
1543
+ return;
1544
+ }
1545
+ console.log(`Secrets provider ${remove ? "removed" : "configured"}: ${alias}`);
1546
+ }
1547
+ async secretsApply(opts) {
1548
+ const prevConfig = loadConfig3();
1549
+ const nextConfig = structuredClone(prevConfig);
1550
+ if (opts.enable && opts.disable) {
1551
+ throw new Error("cannot set --enable and --disable at the same time");
1552
+ }
1553
+ if (opts.enable) {
1554
+ nextConfig.secrets.enabled = true;
1555
+ }
1556
+ if (opts.disable) {
1557
+ nextConfig.secrets.enabled = false;
1558
+ }
1559
+ if (opts.file) {
1560
+ const raw = readFileSync3(opts.file, "utf-8");
1561
+ const patch = parseApplyFile(raw);
1562
+ if (patch.defaults) {
1563
+ nextConfig.secrets.defaults = patch.defaults;
1564
+ }
1565
+ if (patch.providers) {
1566
+ nextConfig.secrets.providers = patch.providers;
1567
+ }
1568
+ if (patch.refs) {
1569
+ nextConfig.secrets.refs = {
1570
+ ...nextConfig.secrets.refs,
1571
+ ...patch.refs
1572
+ };
1573
+ }
1574
+ }
1575
+ if (opts.path) {
1576
+ const path = opts.path.trim();
1577
+ if (!path) {
1578
+ throw new Error("path is empty");
1579
+ }
1580
+ if (opts.remove) {
1581
+ delete nextConfig.secrets.refs[path];
1582
+ } else {
1583
+ const source = normalizeSecretSource(opts.source);
1584
+ const id = normalizeOptionalString(opts.id);
1585
+ if (!source || !id) {
1586
+ throw new Error("apply single ref requires --source and --id");
1587
+ }
1588
+ const provider = normalizeOptionalString(opts.provider);
1589
+ nextConfig.secrets.refs[path] = {
1590
+ source,
1591
+ id,
1592
+ ...provider ? { provider } : {}
1593
+ };
1594
+ }
1595
+ } else if (opts.remove && !opts.file) {
1596
+ throw new Error("--remove requires --path");
1597
+ }
1598
+ resolveConfigSecrets(nextConfig, { configPath: getConfigPath() });
1599
+ saveConfig3(nextConfig);
1600
+ await this.requestRestartForConfigDiff({
1601
+ prevConfig,
1602
+ nextConfig,
1603
+ reason: "secrets.apply",
1604
+ manualMessage: "Secrets updated. Restart the gateway if required."
1605
+ });
1606
+ if (opts.json) {
1607
+ console.log(JSON.stringify({ ok: true, secrets: nextConfig.secrets }, null, 2));
1608
+ return;
1609
+ }
1610
+ console.log("Secrets applied.");
1611
+ }
1612
+ async secretsReload(opts = {}) {
1613
+ const config2 = loadConfig3();
1614
+ const configPath = getConfigPath();
1615
+ resolveConfigSecrets(config2, { configPath });
1616
+ saveConfig3(config2);
1617
+ if (opts.json) {
1618
+ console.log(JSON.stringify({ ok: true, message: "secrets reload signal emitted" }, null, 2));
1619
+ return;
1620
+ }
1621
+ console.log("Secrets reload signal emitted.");
1622
+ }
1623
+ async requestRestartForConfigDiff(params) {
1624
+ const changedPaths = diffConfigPaths2(params.prevConfig, params.nextConfig);
1625
+ if (!changedPaths.length) {
1626
+ return;
1627
+ }
1628
+ const plan = buildReloadPlan2(changedPaths);
1629
+ if (plan.restartRequired.length === 0) {
1630
+ return;
1631
+ }
1632
+ await this.deps.requestRestart({
1633
+ reason: `${params.reason} (${plan.restartRequired.join(", ")})`,
1634
+ manualMessage: params.manualMessage
1635
+ });
1636
+ }
1637
+ };
1638
+
1315
1639
  // src/cli/commands/channels.ts
1316
1640
  import { spawnSync as spawnSync3 } from "child_process";
1317
- import { BUILTIN_CHANNEL_PLUGIN_IDS, getWorkspacePath as getWorkspacePath2, loadConfig as loadConfig3, saveConfig as saveConfig3, PROVIDERS as PROVIDERS2 } from "@nextclaw/core";
1641
+ import { BUILTIN_CHANNEL_PLUGIN_IDS, getWorkspacePath as getWorkspacePath2, loadConfig as loadConfig4, saveConfig as saveConfig4, PROVIDERS as PROVIDERS2 } from "@nextclaw/core";
1318
1642
  import { buildPluginStatusReport as buildPluginStatusReport2, enablePluginInConfig as enablePluginInConfig2, getPluginChannelBindings } from "@nextclaw/openclaw-compat";
1319
1643
  var CHANNEL_LABELS = {
1320
1644
  telegram: "Telegram",
@@ -1333,7 +1657,7 @@ var ChannelCommands = class {
1333
1657
  this.deps = deps;
1334
1658
  }
1335
1659
  channelsStatus() {
1336
- const config2 = loadConfig3();
1660
+ const config2 = loadConfig4();
1337
1661
  console.log("Channel Status");
1338
1662
  const channelConfig = config2.channels;
1339
1663
  for (const channelId of BUILTIN_CHANNEL_PLUGIN_IDS) {
@@ -1372,7 +1696,7 @@ var ChannelCommands = class {
1372
1696
  console.error("--channel is required");
1373
1697
  process.exit(1);
1374
1698
  }
1375
- const config2 = loadConfig3();
1699
+ const config2 = loadConfig4();
1376
1700
  const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
1377
1701
  const pluginRegistry = loadPluginRegistry(config2, workspaceDir);
1378
1702
  const bindings = getPluginChannelBindings(pluginRegistry);
@@ -1415,7 +1739,7 @@ var ChannelCommands = class {
1415
1739
  }
1416
1740
  let next = mergePluginConfigView(config2, nextView, bindings);
1417
1741
  next = enablePluginInConfig2(next, binding.pluginId);
1418
- saveConfig3(next);
1742
+ saveConfig4(next);
1419
1743
  console.log(`Configured channel "${binding.channelId}" via plugin "${binding.pluginId}".`);
1420
1744
  await this.deps.requestRestart({
1421
1745
  reason: `channel configured via plugin: ${binding.pluginId}`,
@@ -1502,14 +1826,15 @@ var CronCommands = class {
1502
1826
 
1503
1827
  // src/cli/commands/diagnostics.ts
1504
1828
  import { createServer as createNetServer } from "net";
1505
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1829
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1506
1830
  import { resolve as resolve6 } from "path";
1507
1831
  import {
1508
1832
  APP_NAME,
1509
- getConfigPath,
1833
+ getConfigPath as getConfigPath2,
1510
1834
  getDataDir as getDataDir4,
1511
1835
  getWorkspacePath as getWorkspacePath3,
1512
- loadConfig as loadConfig4,
1836
+ hasSecretRef,
1837
+ loadConfig as loadConfig5,
1513
1838
  PROVIDERS as PROVIDERS3
1514
1839
  } from "@nextclaw/core";
1515
1840
  var DiagnosticsCommands = class {
@@ -1670,8 +1995,8 @@ var DiagnosticsCommands = class {
1670
1995
  process.exitCode = exitCode;
1671
1996
  }
1672
1997
  async collectRuntimeStatus(params) {
1673
- const configPath = getConfigPath();
1674
- const config2 = loadConfig4();
1998
+ const configPath = getConfigPath2();
1999
+ const config2 = loadConfig5();
1675
2000
  const workspacePath = getWorkspacePath3(config2.agents.defaults.workspace);
1676
2001
  const serviceStatePath = resolve6(getDataDir4(), "run", "service.json");
1677
2002
  const fixActions = [];
@@ -1694,6 +2019,7 @@ var DiagnosticsCommands = class {
1694
2019
  const orphanSuspected = !running && configuredHealth.state === "ok";
1695
2020
  const providers = PROVIDERS3.map((spec) => {
1696
2021
  const provider = config2.providers[spec.name];
2022
+ const apiKeyRefSet = hasSecretRef(config2, `providers.${spec.name}.apiKey`);
1697
2023
  if (!provider) {
1698
2024
  return { name: spec.displayName ?? spec.name, configured: false, detail: "missing config" };
1699
2025
  }
@@ -1706,8 +2032,8 @@ var DiagnosticsCommands = class {
1706
2032
  }
1707
2033
  return {
1708
2034
  name: spec.displayName ?? spec.name,
1709
- configured: Boolean(provider.apiKey),
1710
- detail: provider.apiKey ? "apiKey set" : "apiKey not set"
2035
+ configured: Boolean(provider.apiKey) || apiKeyRefSet,
2036
+ detail: provider.apiKey ? "apiKey set" : apiKeyRefSet ? "apiKey ref set" : "apiKey not set"
1711
2037
  };
1712
2038
  });
1713
2039
  const issues = [];
@@ -1804,7 +2130,7 @@ var DiagnosticsCommands = class {
1804
2130
  return [];
1805
2131
  }
1806
2132
  try {
1807
- const lines = readFileSync3(path, "utf-8").split(/\r?\n/).filter(Boolean);
2133
+ const lines = readFileSync4(path, "utf-8").split(/\r?\n/).filter(Boolean);
1808
2134
  if (lines.length <= maxLines) {
1809
2135
  return lines;
1810
2136
  }
@@ -1852,19 +2178,20 @@ import chokidar from "chokidar";
1852
2178
 
1853
2179
  // src/cli/gateway/controller.ts
1854
2180
  import { createHash } from "crypto";
1855
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
2181
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
1856
2182
  import {
1857
2183
  buildConfigSchema,
1858
2184
  ConfigSchema,
2185
+ normalizeInlineSecretRefs,
1859
2186
  redactConfigObject
1860
2187
  } from "@nextclaw/core";
1861
2188
  var hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
1862
- var readConfigSnapshot = (getConfigPath4) => {
1863
- const path = getConfigPath4();
2189
+ var readConfigSnapshot = (getConfigPath5) => {
2190
+ const path = getConfigPath5();
1864
2191
  let raw = "";
1865
2192
  let parsed = {};
1866
2193
  if (existsSync6(path)) {
1867
- raw = readFileSync4(path, "utf-8");
2194
+ raw = readFileSync5(path, "utf-8");
1868
2195
  try {
1869
2196
  parsed = JSON.parse(raw);
1870
2197
  } catch {
@@ -1874,7 +2201,7 @@ var readConfigSnapshot = (getConfigPath4) => {
1874
2201
  let config2;
1875
2202
  let valid = true;
1876
2203
  try {
1877
- config2 = ConfigSchema.parse(parsed);
2204
+ config2 = ConfigSchema.parse(normalizeInlineSecretRefs(parsed));
1878
2205
  } catch {
1879
2206
  config2 = ConfigSchema.parse({});
1880
2207
  valid = false;
@@ -2031,7 +2358,7 @@ var GatewayControllerImpl = class {
2031
2358
  }
2032
2359
  let validated;
2033
2360
  try {
2034
- validated = ConfigSchema.parse(parsedRaw);
2361
+ validated = ConfigSchema.parse(normalizeInlineSecretRefs(parsedRaw));
2035
2362
  } catch (err) {
2036
2363
  return { ok: false, error: `invalid config: ${String(err)}` };
2037
2364
  }
@@ -2074,7 +2401,7 @@ var GatewayControllerImpl = class {
2074
2401
  const merged = mergeDeep(snapshot.config, patch);
2075
2402
  let validated;
2076
2403
  try {
2077
- validated = ConfigSchema.parse(merged);
2404
+ validated = ConfigSchema.parse(normalizeInlineSecretRefs(merged));
2078
2405
  } catch (err) {
2079
2406
  return { ok: false, error: `invalid config: ${String(err)}` };
2080
2407
  }
@@ -2141,8 +2468,8 @@ var GatewayControllerImpl = class {
2141
2468
 
2142
2469
  // src/cli/config-reloader.ts
2143
2470
  import {
2144
- buildReloadPlan as buildReloadPlan2,
2145
- diffConfigPaths as diffConfigPaths2,
2471
+ buildReloadPlan as buildReloadPlan3,
2472
+ diffConfigPaths as diffConfigPaths3,
2146
2473
  ChannelManager
2147
2474
  } from "@nextclaw/core";
2148
2475
  var ConfigReloader = class {
@@ -2168,13 +2495,13 @@ var ConfigReloader = class {
2168
2495
  this.options.reloadPlugins = callback;
2169
2496
  }
2170
2497
  async applyReloadPlan(nextConfig) {
2171
- const changedPaths = diffConfigPaths2(this.currentConfig, nextConfig);
2498
+ const changedPaths = diffConfigPaths3(this.currentConfig, nextConfig);
2172
2499
  if (!changedPaths.length) {
2173
2500
  return;
2174
2501
  }
2175
2502
  this.currentConfig = nextConfig;
2176
2503
  this.options.providerManager?.setConfig(nextConfig);
2177
- const plan = buildReloadPlan2(changedPaths);
2504
+ const plan = buildReloadPlan3(changedPaths);
2178
2505
  if (plan.reloadPlugins) {
2179
2506
  await this.reloadPlugins(nextConfig);
2180
2507
  console.log("Config reload: plugins reloaded.");
@@ -2482,17 +2809,18 @@ var {
2482
2809
  ChannelManager: ChannelManager2,
2483
2810
  CronService: CronService2,
2484
2811
  getApiBase,
2485
- getConfigPath: getConfigPath2,
2812
+ getConfigPath: getConfigPath3,
2486
2813
  getDataDir: getDataDir5,
2487
2814
  getProvider,
2488
2815
  getProviderName,
2489
2816
  getWorkspacePath: getWorkspacePath5,
2490
2817
  HeartbeatService,
2491
2818
  LiteLLMProvider,
2492
- loadConfig: loadConfig5,
2819
+ loadConfig: loadConfig6,
2493
2820
  MessageBus,
2494
2821
  ProviderManager,
2495
- saveConfig: saveConfig4,
2822
+ resolveConfigSecrets: resolveConfigSecrets2,
2823
+ saveConfig: saveConfig5,
2496
2824
  SessionManager,
2497
2825
  parseAgentScopedSessionKey: parseAgentScopedSessionKey2
2498
2826
  } = NextclawCore;
@@ -2508,7 +2836,8 @@ var ServiceCommands = class {
2508
2836
  this.deps = deps;
2509
2837
  }
2510
2838
  async startGateway(options = {}) {
2511
- const config2 = loadConfig5();
2839
+ const runtimeConfigPath = getConfigPath3();
2840
+ const config2 = resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath });
2512
2841
  const workspace = getWorkspacePath5(config2.agents.defaults.workspace);
2513
2842
  let pluginRegistry = loadPluginRegistry(config2, workspace);
2514
2843
  let extensionRegistry = toExtensionRegistry(pluginRegistry);
@@ -2553,7 +2882,7 @@ var ServiceCommands = class {
2553
2882
  sessionManager,
2554
2883
  providerManager,
2555
2884
  makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }) ?? this.makeMissingProvider(nextConfig),
2556
- loadConfig: loadConfig5,
2885
+ loadConfig: () => resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath }),
2557
2886
  getExtensionChannels: () => extensionRegistry.channels,
2558
2887
  onRestartRequired: (paths) => {
2559
2888
  void this.deps.requestRestart({
@@ -2567,8 +2896,8 @@ var ServiceCommands = class {
2567
2896
  reloader,
2568
2897
  cron: cron2,
2569
2898
  sessionManager,
2570
- getConfigPath: getConfigPath2,
2571
- saveConfig: saveConfig4,
2899
+ getConfigPath: getConfigPath3,
2900
+ saveConfig: saveConfig5,
2572
2901
  requestRestart: async (options2) => {
2573
2902
  await this.deps.requestRestart({
2574
2903
  reason: options2?.reason ?? "gateway tool restart",
@@ -2594,7 +2923,7 @@ var ServiceCommands = class {
2594
2923
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
2595
2924
  registry: pluginRegistry,
2596
2925
  channel,
2597
- cfg: loadConfig5(),
2926
+ cfg: resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath }),
2598
2927
  accountId
2599
2928
  })
2600
2929
  });
@@ -2620,14 +2949,14 @@ var ServiceCommands = class {
2620
2949
  });
2621
2950
  let pluginChannelBindings = getPluginChannelBindings2(pluginRegistry);
2622
2951
  setPluginRuntimeBridge({
2623
- loadConfig: () => toPluginConfigView(loadConfig5(), pluginChannelBindings),
2952
+ loadConfig: () => toPluginConfigView(resolveConfigSecrets2(loadConfig6(), { configPath: runtimeConfigPath }), pluginChannelBindings),
2624
2953
  writeConfigFile: async (nextConfigView) => {
2625
2954
  if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
2626
2955
  throw new Error("plugin runtime writeConfigFile expects an object config");
2627
2956
  }
2628
- const current = loadConfig5();
2957
+ const current = loadConfig6();
2629
2958
  const next = mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
2630
- saveConfig4(next);
2959
+ saveConfig5(next);
2631
2960
  },
2632
2961
  dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
2633
2962
  const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
@@ -2698,7 +3027,7 @@ var ServiceCommands = class {
2698
3027
  console.log(`\u2713 Cron: ${cronStatus.jobs} scheduled jobs`);
2699
3028
  }
2700
3029
  console.log("\u2713 Heartbeat: every 30m");
2701
- const configPath = resolve7(getConfigPath2());
3030
+ const configPath = resolve7(getConfigPath3());
2702
3031
  const watcher = chokidar.watch(configPath, {
2703
3032
  ignoreInitial: true,
2704
3033
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }
@@ -2842,7 +3171,7 @@ var ServiceCommands = class {
2842
3171
  });
2843
3172
  }
2844
3173
  async runForeground(options) {
2845
- const config2 = loadConfig5();
3174
+ const config2 = loadConfig6();
2846
3175
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
2847
3176
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
2848
3177
  if (options.open) {
@@ -2855,7 +3184,7 @@ var ServiceCommands = class {
2855
3184
  });
2856
3185
  }
2857
3186
  async startService(options) {
2858
- const config2 = loadConfig5();
3187
+ const config2 = loadConfig6();
2859
3188
  const uiConfig = resolveUiConfig(config2, options.uiOverrides);
2860
3189
  const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
2861
3190
  const apiUrl = `${uiUrl}/api`;
@@ -3038,7 +3367,7 @@ var ServiceCommands = class {
3038
3367
  return null;
3039
3368
  }
3040
3369
  console.error("Error: No API key configured.");
3041
- console.error(`Set one in ${getConfigPath2()} under providers section`);
3370
+ console.error(`Set one in ${getConfigPath3()} under providers section`);
3042
3371
  process.exit(1);
3043
3372
  }
3044
3373
  return new LiteLLMProvider({
@@ -3071,7 +3400,7 @@ var ServiceCommands = class {
3071
3400
  const uiServer = startUiServer({
3072
3401
  host: uiConfig.host,
3073
3402
  port: uiConfig.port,
3074
- configPath: getConfigPath2(),
3403
+ configPath: getConfigPath3(),
3075
3404
  staticDir: uiStaticDir ?? void 0,
3076
3405
  cronService,
3077
3406
  marketplace: {
@@ -3164,7 +3493,7 @@ var ServiceCommands = class {
3164
3493
  if (!source) {
3165
3494
  throw new Error("Git skill source is required");
3166
3495
  }
3167
- const workspace = getWorkspacePath5(loadConfig5().agents.defaults.workspace);
3496
+ const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
3168
3497
  const skillName = this.resolveGitSkillName(params.skill, source);
3169
3498
  const destination = this.resolveSkillInstallPath(workspace, params.installPath, skillName);
3170
3499
  const destinationSkillFile = join4(destination, "SKILL.md");
@@ -3246,7 +3575,7 @@ var ServiceCommands = class {
3246
3575
  return { message: summary, output };
3247
3576
  }
3248
3577
  async uninstallMarketplaceSkill(slug) {
3249
- const workspace = getWorkspacePath5(loadConfig5().agents.defaults.workspace);
3578
+ const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
3250
3579
  const targetDir = join4(workspace, "skills", slug);
3251
3580
  const skildDir = join4(workspace, ".agents", "skills", slug);
3252
3581
  const existingTargets = [targetDir, skildDir].filter((path) => existsSync7(path));
@@ -3262,7 +3591,7 @@ var ServiceCommands = class {
3262
3591
  };
3263
3592
  }
3264
3593
  installBuiltinMarketplaceSkill(slug, force) {
3265
- const workspace = getWorkspacePath5(loadConfig5().agents.defaults.workspace);
3594
+ const workspace = getWorkspacePath5(loadConfig6().agents.defaults.workspace);
3266
3595
  const destination = join4(workspace, "skills", slug);
3267
3596
  const destinationSkillFile = join4(destination, "SKILL.md");
3268
3597
  if (existsSync7(destinationSkillFile) && !force) {
@@ -3424,7 +3753,7 @@ ${stderr}`.trim();
3424
3753
  };
3425
3754
 
3426
3755
  // src/cli/workspace.ts
3427
- import { cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync5, readdirSync, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
3756
+ import { cpSync as cpSync2, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
3428
3757
  import { createRequire } from "module";
3429
3758
  import { dirname as dirname2, join as join5, resolve as resolve8 } from "path";
3430
3759
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -3465,7 +3794,7 @@ var WorkspaceManager = class {
3465
3794
  console.warn(`Warning: Template file missing: ${templatePath}`);
3466
3795
  continue;
3467
3796
  }
3468
- const raw = readFileSync5(templatePath, "utf-8");
3797
+ const raw = readFileSync6(templatePath, "utf-8");
3469
3798
  const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME3);
3470
3799
  mkdirSync4(dirname2(filePath), { recursive: true });
3471
3800
  writeFileSync3(filePath, content);
@@ -3609,6 +3938,7 @@ var CliRuntime = class {
3609
3938
  workspaceManager;
3610
3939
  serviceCommands;
3611
3940
  configCommands;
3941
+ secretsCommands;
3612
3942
  pluginCommands;
3613
3943
  channelCommands;
3614
3944
  cronCommands;
@@ -3622,6 +3952,9 @@ var CliRuntime = class {
3622
3952
  this.configCommands = new ConfigCommands({
3623
3953
  requestRestart: (params) => this.requestRestart(params)
3624
3954
  });
3955
+ this.secretsCommands = new SecretsCommands({
3956
+ requestRestart: (params) => this.requestRestart(params)
3957
+ });
3625
3958
  this.pluginCommands = new PluginCommands();
3626
3959
  this.channelCommands = new ChannelCommands({
3627
3960
  logo: this.logo,
@@ -3822,14 +4155,14 @@ var CliRuntime = class {
3822
4155
  const source = options.source ?? "init";
3823
4156
  const prefix = options.auto ? "Auto init" : "Init";
3824
4157
  const force = Boolean(options.force);
3825
- const configPath = getConfigPath3();
4158
+ const configPath = getConfigPath4();
3826
4159
  let createdConfig = false;
3827
4160
  if (!existsSync9(configPath)) {
3828
4161
  const config3 = ConfigSchema2.parse({});
3829
- saveConfig5(config3);
4162
+ saveConfig6(config3);
3830
4163
  createdConfig = true;
3831
4164
  }
3832
- const config2 = loadConfig6();
4165
+ const config2 = loadConfig7();
3833
4166
  const workspaceSetting = config2.agents.defaults.workspace;
3834
4167
  const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join6(getDataDir7(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
3835
4168
  const workspaceExisted = existsSync9(workspacePath);
@@ -3938,27 +4271,31 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
3938
4271
  await this.serviceCommands.stopService();
3939
4272
  }
3940
4273
  async agent(opts) {
3941
- const config2 = loadConfig6();
4274
+ const configPath = getConfigPath4();
4275
+ const config2 = resolveConfigSecrets3(loadConfig7(), { configPath });
3942
4276
  const workspace = getWorkspacePath6(config2.agents.defaults.workspace);
3943
4277
  const pluginRegistry = loadPluginRegistry(config2, workspace);
3944
4278
  const extensionRegistry = toExtensionRegistry(pluginRegistry);
3945
4279
  logPluginDiagnostics(pluginRegistry);
3946
4280
  const pluginChannelBindings = getPluginChannelBindings3(pluginRegistry);
3947
4281
  setPluginRuntimeBridge2({
3948
- loadConfig: () => toPluginConfigView(loadConfig6(), pluginChannelBindings),
4282
+ loadConfig: () => toPluginConfigView(
4283
+ resolveConfigSecrets3(loadConfig7(), { configPath }),
4284
+ pluginChannelBindings
4285
+ ),
3949
4286
  writeConfigFile: async (nextConfigView) => {
3950
4287
  if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
3951
4288
  throw new Error(
3952
4289
  "plugin runtime writeConfigFile expects an object config"
3953
4290
  );
3954
4291
  }
3955
- const current = loadConfig6();
4292
+ const current = loadConfig7();
3956
4293
  const next = mergePluginConfigView(
3957
4294
  current,
3958
4295
  nextConfigView,
3959
4296
  pluginChannelBindings
3960
4297
  );
3961
- saveConfig5(next);
4298
+ saveConfig6(next);
3962
4299
  }
3963
4300
  });
3964
4301
  try {
@@ -3985,7 +4322,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
3985
4322
  resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints2({
3986
4323
  registry: pluginRegistry,
3987
4324
  channel,
3988
- cfg: loadConfig6(),
4325
+ cfg: resolveConfigSecrets3(loadConfig7(), { configPath }),
3989
4326
  accountId
3990
4327
  })
3991
4328
  });
@@ -4007,7 +4344,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
4007
4344
  const historyFile = join6(getDataDir7(), "history", "cli_history");
4008
4345
  const historyDir = resolve9(historyFile, "..");
4009
4346
  mkdirSync5(historyDir, { recursive: true });
4010
- const history = existsSync9(historyFile) ? readFileSync6(historyFile, "utf-8").split("\n").filter(Boolean) : [];
4347
+ const history = existsSync9(historyFile) ? readFileSync7(historyFile, "utf-8").split("\n").filter(Boolean) : [];
4011
4348
  const rl = createInterface2({
4012
4349
  input: process.stdin,
4013
4350
  output: process.stdout
@@ -4119,6 +4456,18 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
4119
4456
  async configUnset(pathExpr) {
4120
4457
  await this.configCommands.configUnset(pathExpr);
4121
4458
  }
4459
+ secretsAudit(opts = {}) {
4460
+ this.secretsCommands.secretsAudit(opts);
4461
+ }
4462
+ async secretsConfigure(opts) {
4463
+ await this.secretsCommands.secretsConfigure(opts);
4464
+ }
4465
+ async secretsApply(opts) {
4466
+ await this.secretsCommands.secretsApply(opts);
4467
+ }
4468
+ async secretsReload(opts = {}) {
4469
+ await this.secretsCommands.secretsReload(opts);
4470
+ }
4122
4471
  channelsStatus() {
4123
4472
  this.channelCommands.channelsStatus();
4124
4473
  }
@@ -4205,6 +4554,16 @@ var config = program.command("config").description("Manage config values");
4205
4554
  config.command("get <path>").description("Get a config value by dot path").option("--json", "Output JSON", false).action((path, opts) => runtime.configGet(path, opts));
4206
4555
  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));
4207
4556
  config.command("unset <path>").description("Remove a config value by dot path").action((path) => runtime.configUnset(path));
4557
+ var secrets = program.command("secrets").description("Manage secrets refs/providers");
4558
+ secrets.command("audit").description("Audit secret refs resolution status").option("--json", "Output JSON", false).option("--strict", "Exit non-zero when unresolved refs exist", false).action((opts) => runtime.secretsAudit(opts));
4559
+ secrets.command("configure").description("Configure a secret provider alias").requiredOption("--provider <alias>", "Provider alias").option("--source <source>", "Provider source (env|file|exec)").option("--prefix <prefix>", "Env key prefix (env source)").option("--path <path>", "Secret JSON file path (file source)").option("--command <command>", "Command for exec source").option(
4560
+ "--arg <value>",
4561
+ "Exec argument (repeatable)",
4562
+ (value, previous = []) => [...previous, value],
4563
+ []
4564
+ ).option("--cwd <dir>", "Exec working directory").option("--timeout-ms <ms>", "Exec timeout in milliseconds").option("--set-default", "Set as default alias for this source", false).option("--remove", "Remove provider alias", false).option("--json", "Output JSON", false).action((opts) => runtime.secretsConfigure(opts));
4565
+ secrets.command("apply").description("Apply secret refs/providers/defaults patch").option("--file <path>", "Apply patch from JSON file").option("--path <config-path>", "Single ref target config path").option("--source <source>", "Single ref source (env|file|exec)").option("--id <secret-id>", "Single ref secret id").option("--provider <alias>", "Single ref provider alias").option("--remove", "Remove single ref (--path required)", false).option("--enable", "Enable secrets resolution", false).option("--disable", "Disable secrets resolution", false).option("--json", "Output JSON", false).action((opts) => runtime.secretsApply(opts));
4566
+ secrets.command("reload").description("Trigger runtime secrets reload signal").option("--json", "Output JSON", false).action((opts) => runtime.secretsReload(opts));
4208
4567
  var channels = program.command("channels").description("Manage channels");
4209
4568
  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));
4210
4569
  channels.command("status").description("Show channel status").action(() => runtime.channelsStatus());