nextclaw 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +835 -91
- package/package.json +4 -3
- package/templates/USAGE.md +17 -0
package/dist/cli/index.js
CHANGED
|
@@ -6,13 +6,13 @@ 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
|
|
10
|
-
saveConfig as
|
|
9
|
+
loadConfig as loadConfig6,
|
|
10
|
+
saveConfig as saveConfig5,
|
|
11
11
|
getConfigPath as getConfigPath3,
|
|
12
12
|
getDataDir as getDataDir6,
|
|
13
13
|
ConfigSchema as ConfigSchema2,
|
|
14
|
-
getWorkspacePath as
|
|
15
|
-
expandHome,
|
|
14
|
+
getWorkspacePath as getWorkspacePath5,
|
|
15
|
+
expandHome as expandHome2,
|
|
16
16
|
MessageBus as MessageBus2,
|
|
17
17
|
AgentLoop as AgentLoop2,
|
|
18
18
|
ProviderManager as ProviderManager2,
|
|
@@ -20,9 +20,10 @@ import {
|
|
|
20
20
|
DEFAULT_WORKSPACE_DIR,
|
|
21
21
|
DEFAULT_WORKSPACE_PATH
|
|
22
22
|
} from "@nextclaw/core";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
23
|
+
import { resolvePluginChannelMessageToolHints as resolvePluginChannelMessageToolHints2 } from "@nextclaw/openclaw-compat";
|
|
24
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
25
|
+
import { join as join6, resolve as resolve8 } from "path";
|
|
26
|
+
import { createInterface as createInterface2 } from "readline";
|
|
26
27
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
27
28
|
import { spawn as spawn3 } from "child_process";
|
|
28
29
|
|
|
@@ -254,7 +255,7 @@ async function waitForExit(pid, timeoutMs) {
|
|
|
254
255
|
if (!isProcessRunning(pid)) {
|
|
255
256
|
return true;
|
|
256
257
|
}
|
|
257
|
-
await new Promise((
|
|
258
|
+
await new Promise((resolve9) => setTimeout(resolve9, 200));
|
|
258
259
|
}
|
|
259
260
|
return !isProcessRunning(pid);
|
|
260
261
|
}
|
|
@@ -344,8 +345,8 @@ function printAgentResponse(response) {
|
|
|
344
345
|
async function prompt(rl, question) {
|
|
345
346
|
rl.setPrompt(question);
|
|
346
347
|
rl.prompt();
|
|
347
|
-
return new Promise((
|
|
348
|
-
rl.once("line", (line) =>
|
|
348
|
+
return new Promise((resolve9) => {
|
|
349
|
+
rl.once("line", (line) => resolve9(line));
|
|
349
350
|
});
|
|
350
351
|
}
|
|
351
352
|
|
|
@@ -391,8 +392,549 @@ function runSelfUpdate(options = {}) {
|
|
|
391
392
|
return { ok: false, error: "no update strategy available", strategy: "none", steps };
|
|
392
393
|
}
|
|
393
394
|
|
|
395
|
+
// src/cli/commands/plugins.ts
|
|
396
|
+
import {
|
|
397
|
+
addPluginLoadPath,
|
|
398
|
+
buildPluginStatusReport,
|
|
399
|
+
disablePluginInConfig,
|
|
400
|
+
enablePluginInConfig,
|
|
401
|
+
installPluginFromNpmSpec,
|
|
402
|
+
installPluginFromPath,
|
|
403
|
+
loadOpenClawPlugins,
|
|
404
|
+
recordPluginInstall,
|
|
405
|
+
resolveUninstallDirectoryTarget,
|
|
406
|
+
uninstallPlugin
|
|
407
|
+
} from "@nextclaw/openclaw-compat";
|
|
408
|
+
import {
|
|
409
|
+
loadConfig,
|
|
410
|
+
saveConfig,
|
|
411
|
+
getWorkspacePath,
|
|
412
|
+
PROVIDERS,
|
|
413
|
+
expandHome
|
|
414
|
+
} from "@nextclaw/core";
|
|
415
|
+
import { createInterface } from "readline";
|
|
416
|
+
import { existsSync as existsSync3 } from "fs";
|
|
417
|
+
import { resolve as resolve4 } from "path";
|
|
418
|
+
function loadPluginRegistry(config2, workspaceDir) {
|
|
419
|
+
return loadOpenClawPlugins({
|
|
420
|
+
config: config2,
|
|
421
|
+
workspaceDir,
|
|
422
|
+
reservedToolNames: [
|
|
423
|
+
"read_file",
|
|
424
|
+
"write_file",
|
|
425
|
+
"edit_file",
|
|
426
|
+
"list_dir",
|
|
427
|
+
"exec",
|
|
428
|
+
"web_search",
|
|
429
|
+
"web_fetch",
|
|
430
|
+
"message",
|
|
431
|
+
"spawn",
|
|
432
|
+
"sessions_list",
|
|
433
|
+
"sessions_history",
|
|
434
|
+
"sessions_send",
|
|
435
|
+
"memory_search",
|
|
436
|
+
"memory_get",
|
|
437
|
+
"subagents",
|
|
438
|
+
"gateway",
|
|
439
|
+
"cron"
|
|
440
|
+
],
|
|
441
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
442
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name),
|
|
443
|
+
logger: {
|
|
444
|
+
info: (message) => console.log(message),
|
|
445
|
+
warn: (message) => console.warn(message),
|
|
446
|
+
error: (message) => console.error(message),
|
|
447
|
+
debug: (message) => console.debug(message)
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function toExtensionRegistry(pluginRegistry) {
|
|
452
|
+
return {
|
|
453
|
+
tools: pluginRegistry.tools.map((tool) => ({
|
|
454
|
+
extensionId: tool.pluginId,
|
|
455
|
+
factory: tool.factory,
|
|
456
|
+
names: tool.names,
|
|
457
|
+
optional: tool.optional,
|
|
458
|
+
source: tool.source
|
|
459
|
+
})),
|
|
460
|
+
channels: pluginRegistry.channels.map((channel) => ({
|
|
461
|
+
extensionId: channel.pluginId,
|
|
462
|
+
channel: channel.channel,
|
|
463
|
+
source: channel.source
|
|
464
|
+
})),
|
|
465
|
+
diagnostics: pluginRegistry.diagnostics.map((diag) => ({
|
|
466
|
+
level: diag.level,
|
|
467
|
+
message: diag.message,
|
|
468
|
+
extensionId: diag.pluginId,
|
|
469
|
+
source: diag.source
|
|
470
|
+
}))
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function logPluginDiagnostics(registry) {
|
|
474
|
+
for (const diag of registry.diagnostics) {
|
|
475
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
476
|
+
const text = `${prefix}${diag.message}`;
|
|
477
|
+
if (diag.level === "error") {
|
|
478
|
+
console.error(`[plugins] ${text}`);
|
|
479
|
+
} else {
|
|
480
|
+
console.warn(`[plugins] ${text}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function toPluginConfigView(config2, bindings) {
|
|
485
|
+
const view = JSON.parse(JSON.stringify(config2));
|
|
486
|
+
const channels2 = view.channels && typeof view.channels === "object" && !Array.isArray(view.channels) ? { ...view.channels } : {};
|
|
487
|
+
for (const binding of bindings) {
|
|
488
|
+
const pluginConfig = config2.plugins.entries?.[binding.pluginId]?.config;
|
|
489
|
+
if (!pluginConfig || typeof pluginConfig !== "object" || Array.isArray(pluginConfig)) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
channels2[binding.channelId] = JSON.parse(JSON.stringify(pluginConfig));
|
|
493
|
+
}
|
|
494
|
+
view.channels = channels2;
|
|
495
|
+
return view;
|
|
496
|
+
}
|
|
497
|
+
function mergePluginConfigView(baseConfig, pluginViewConfig, bindings) {
|
|
498
|
+
const next = JSON.parse(JSON.stringify(baseConfig));
|
|
499
|
+
const pluginChannels = pluginViewConfig.channels && typeof pluginViewConfig.channels === "object" && !Array.isArray(pluginViewConfig.channels) ? pluginViewConfig.channels : {};
|
|
500
|
+
const entries = { ...next.plugins.entries ?? {} };
|
|
501
|
+
for (const binding of bindings) {
|
|
502
|
+
if (!Object.prototype.hasOwnProperty.call(pluginChannels, binding.channelId)) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const channelConfig = pluginChannels[binding.channelId];
|
|
506
|
+
if (!channelConfig || typeof channelConfig !== "object" || Array.isArray(channelConfig)) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
entries[binding.pluginId] = {
|
|
510
|
+
...entries[binding.pluginId] ?? {},
|
|
511
|
+
config: channelConfig
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
next.plugins = {
|
|
515
|
+
...next.plugins,
|
|
516
|
+
entries
|
|
517
|
+
};
|
|
518
|
+
return next;
|
|
519
|
+
}
|
|
520
|
+
var PluginCommands = class {
|
|
521
|
+
constructor(deps) {
|
|
522
|
+
this.deps = deps;
|
|
523
|
+
}
|
|
524
|
+
pluginsList(opts = {}) {
|
|
525
|
+
const config2 = loadConfig();
|
|
526
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
527
|
+
const report = buildPluginStatusReport({
|
|
528
|
+
config: config2,
|
|
529
|
+
workspaceDir,
|
|
530
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
531
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
532
|
+
});
|
|
533
|
+
const list = opts.enabled ? report.plugins.filter((plugin) => plugin.status === "loaded") : report.plugins;
|
|
534
|
+
if (opts.json) {
|
|
535
|
+
console.log(
|
|
536
|
+
JSON.stringify(
|
|
537
|
+
{
|
|
538
|
+
workspaceDir,
|
|
539
|
+
plugins: list,
|
|
540
|
+
diagnostics: report.diagnostics
|
|
541
|
+
},
|
|
542
|
+
null,
|
|
543
|
+
2
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (list.length === 0) {
|
|
549
|
+
console.log("No plugins discovered.");
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
for (const plugin of list) {
|
|
553
|
+
const status = plugin.status === "loaded" ? "loaded" : plugin.status === "disabled" ? "disabled" : "error";
|
|
554
|
+
const title = plugin.name && plugin.name !== plugin.id ? `${plugin.name} (${plugin.id})` : plugin.id;
|
|
555
|
+
if (!opts.verbose) {
|
|
556
|
+
const desc = plugin.description ? plugin.description.length > 80 ? `${plugin.description.slice(0, 77)}...` : plugin.description : "(no description)";
|
|
557
|
+
console.log(`${title} ${status} - ${desc}`);
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
console.log(`${title} ${status}`);
|
|
561
|
+
console.log(` source: ${plugin.source}`);
|
|
562
|
+
console.log(` origin: ${plugin.origin}`);
|
|
563
|
+
if (plugin.version) {
|
|
564
|
+
console.log(` version: ${plugin.version}`);
|
|
565
|
+
}
|
|
566
|
+
if (plugin.toolNames.length > 0) {
|
|
567
|
+
console.log(` tools: ${plugin.toolNames.join(", ")}`);
|
|
568
|
+
}
|
|
569
|
+
if (plugin.channelIds.length > 0) {
|
|
570
|
+
console.log(` channels: ${plugin.channelIds.join(", ")}`);
|
|
571
|
+
}
|
|
572
|
+
if (plugin.providerIds.length > 0) {
|
|
573
|
+
console.log(` providers: ${plugin.providerIds.join(", ")}`);
|
|
574
|
+
}
|
|
575
|
+
if (plugin.error) {
|
|
576
|
+
console.log(` error: ${plugin.error}`);
|
|
577
|
+
}
|
|
578
|
+
console.log("");
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
pluginsInfo(id, opts = {}) {
|
|
582
|
+
const config2 = loadConfig();
|
|
583
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
584
|
+
const report = buildPluginStatusReport({
|
|
585
|
+
config: config2,
|
|
586
|
+
workspaceDir,
|
|
587
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
588
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
589
|
+
});
|
|
590
|
+
const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
|
|
591
|
+
if (!plugin) {
|
|
592
|
+
console.error(`Plugin not found: ${id}`);
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
if (opts.json) {
|
|
596
|
+
console.log(JSON.stringify(plugin, null, 2));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
const install = config2.plugins.installs?.[plugin.id];
|
|
600
|
+
const lines = [];
|
|
601
|
+
lines.push(plugin.name || plugin.id);
|
|
602
|
+
if (plugin.name && plugin.name !== plugin.id) {
|
|
603
|
+
lines.push(`id: ${plugin.id}`);
|
|
604
|
+
}
|
|
605
|
+
if (plugin.description) {
|
|
606
|
+
lines.push(plugin.description);
|
|
607
|
+
}
|
|
608
|
+
lines.push("");
|
|
609
|
+
lines.push(`Status: ${plugin.status}`);
|
|
610
|
+
lines.push(`Source: ${plugin.source}`);
|
|
611
|
+
lines.push(`Origin: ${plugin.origin}`);
|
|
612
|
+
if (plugin.version) {
|
|
613
|
+
lines.push(`Version: ${plugin.version}`);
|
|
614
|
+
}
|
|
615
|
+
if (plugin.toolNames.length > 0) {
|
|
616
|
+
lines.push(`Tools: ${plugin.toolNames.join(", ")}`);
|
|
617
|
+
}
|
|
618
|
+
if (plugin.channelIds.length > 0) {
|
|
619
|
+
lines.push(`Channels: ${plugin.channelIds.join(", ")}`);
|
|
620
|
+
}
|
|
621
|
+
if (plugin.providerIds.length > 0) {
|
|
622
|
+
lines.push(`Providers: ${plugin.providerIds.join(", ")}`);
|
|
623
|
+
}
|
|
624
|
+
if (plugin.error) {
|
|
625
|
+
lines.push(`Error: ${plugin.error}`);
|
|
626
|
+
}
|
|
627
|
+
if (install) {
|
|
628
|
+
lines.push("");
|
|
629
|
+
lines.push(`Install: ${install.source}`);
|
|
630
|
+
if (install.spec) {
|
|
631
|
+
lines.push(`Spec: ${install.spec}`);
|
|
632
|
+
}
|
|
633
|
+
if (install.sourcePath) {
|
|
634
|
+
lines.push(`Source path: ${install.sourcePath}`);
|
|
635
|
+
}
|
|
636
|
+
if (install.installPath) {
|
|
637
|
+
lines.push(`Install path: ${install.installPath}`);
|
|
638
|
+
}
|
|
639
|
+
if (install.version) {
|
|
640
|
+
lines.push(`Recorded version: ${install.version}`);
|
|
641
|
+
}
|
|
642
|
+
if (install.installedAt) {
|
|
643
|
+
lines.push(`Installed at: ${install.installedAt}`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
console.log(lines.join("\n"));
|
|
647
|
+
}
|
|
648
|
+
async pluginsEnable(id) {
|
|
649
|
+
const config2 = loadConfig();
|
|
650
|
+
const next = enablePluginInConfig(config2, id);
|
|
651
|
+
saveConfig(next);
|
|
652
|
+
await this.deps.requestRestart({
|
|
653
|
+
reason: `plugin enabled: ${id}`,
|
|
654
|
+
manualMessage: `Enabled plugin "${id}". Restart the gateway to apply.`
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
async pluginsDisable(id) {
|
|
658
|
+
const config2 = loadConfig();
|
|
659
|
+
const next = disablePluginInConfig(config2, id);
|
|
660
|
+
saveConfig(next);
|
|
661
|
+
await this.deps.requestRestart({
|
|
662
|
+
reason: `plugin disabled: ${id}`,
|
|
663
|
+
manualMessage: `Disabled plugin "${id}". Restart the gateway to apply.`
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
async pluginsUninstall(id, opts = {}) {
|
|
667
|
+
const config2 = loadConfig();
|
|
668
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
669
|
+
const report = buildPluginStatusReport({
|
|
670
|
+
config: config2,
|
|
671
|
+
workspaceDir,
|
|
672
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
673
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
674
|
+
});
|
|
675
|
+
const keepFiles = Boolean(opts.keepFiles || opts.keepConfig);
|
|
676
|
+
if (opts.keepConfig) {
|
|
677
|
+
console.log("`--keep-config` is deprecated, use `--keep-files`.");
|
|
678
|
+
}
|
|
679
|
+
const plugin = report.plugins.find((entry) => entry.id === id || entry.name === id);
|
|
680
|
+
const pluginId = plugin?.id ?? id;
|
|
681
|
+
const hasEntry = pluginId in (config2.plugins.entries ?? {});
|
|
682
|
+
const hasInstall = pluginId in (config2.plugins.installs ?? {});
|
|
683
|
+
if (!hasEntry && !hasInstall) {
|
|
684
|
+
if (plugin) {
|
|
685
|
+
console.error(
|
|
686
|
+
`Plugin "${pluginId}" is not managed by plugins config/install records and cannot be uninstalled.`
|
|
687
|
+
);
|
|
688
|
+
} else {
|
|
689
|
+
console.error(`Plugin not found: ${id}`);
|
|
690
|
+
}
|
|
691
|
+
process.exit(1);
|
|
692
|
+
}
|
|
693
|
+
const install = config2.plugins.installs?.[pluginId];
|
|
694
|
+
const isLinked = install?.source === "path" && (!install.installPath || !install.sourcePath || resolve4(install.installPath) === resolve4(install.sourcePath));
|
|
695
|
+
const preview = [];
|
|
696
|
+
if (hasEntry) {
|
|
697
|
+
preview.push("config entry");
|
|
698
|
+
}
|
|
699
|
+
if (hasInstall) {
|
|
700
|
+
preview.push("install record");
|
|
701
|
+
}
|
|
702
|
+
if (config2.plugins.allow?.includes(pluginId)) {
|
|
703
|
+
preview.push("allowlist entry");
|
|
704
|
+
}
|
|
705
|
+
if (isLinked && install?.sourcePath && config2.plugins.load?.paths?.includes(install.sourcePath)) {
|
|
706
|
+
preview.push("load path");
|
|
707
|
+
}
|
|
708
|
+
const deleteTarget = !keepFiles ? resolveUninstallDirectoryTarget({
|
|
709
|
+
pluginId,
|
|
710
|
+
hasInstall,
|
|
711
|
+
installRecord: install
|
|
712
|
+
}) : null;
|
|
713
|
+
if (deleteTarget) {
|
|
714
|
+
preview.push(`directory: ${deleteTarget}`);
|
|
715
|
+
}
|
|
716
|
+
const pluginName = plugin?.name || pluginId;
|
|
717
|
+
const pluginTitle = pluginName !== pluginId ? `${pluginName} (${pluginId})` : pluginName;
|
|
718
|
+
console.log(`Plugin: ${pluginTitle}`);
|
|
719
|
+
console.log(`Will remove: ${preview.length > 0 ? preview.join(", ") : "(nothing)"}`);
|
|
720
|
+
if (opts.dryRun) {
|
|
721
|
+
console.log("Dry run, no changes made.");
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (!opts.force) {
|
|
725
|
+
const confirmed = await this.confirmYesNo(`Uninstall plugin "${pluginId}"?`);
|
|
726
|
+
if (!confirmed) {
|
|
727
|
+
console.log("Cancelled.");
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const result = await uninstallPlugin({
|
|
732
|
+
config: config2,
|
|
733
|
+
pluginId,
|
|
734
|
+
deleteFiles: !keepFiles
|
|
735
|
+
});
|
|
736
|
+
if (!result.ok) {
|
|
737
|
+
console.error(result.error);
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
for (const warning of result.warnings) {
|
|
741
|
+
console.warn(warning);
|
|
742
|
+
}
|
|
743
|
+
saveConfig(result.config);
|
|
744
|
+
const removed = [];
|
|
745
|
+
if (result.actions.entry) {
|
|
746
|
+
removed.push("config entry");
|
|
747
|
+
}
|
|
748
|
+
if (result.actions.install) {
|
|
749
|
+
removed.push("install record");
|
|
750
|
+
}
|
|
751
|
+
if (result.actions.allowlist) {
|
|
752
|
+
removed.push("allowlist");
|
|
753
|
+
}
|
|
754
|
+
if (result.actions.loadPath) {
|
|
755
|
+
removed.push("load path");
|
|
756
|
+
}
|
|
757
|
+
if (result.actions.directory) {
|
|
758
|
+
removed.push("directory");
|
|
759
|
+
}
|
|
760
|
+
console.log(`Uninstalled plugin "${pluginId}". Removed: ${removed.length > 0 ? removed.join(", ") : "nothing"}.`);
|
|
761
|
+
await this.deps.requestRestart({
|
|
762
|
+
reason: `plugin uninstalled: ${pluginId}`,
|
|
763
|
+
manualMessage: "Restart the gateway to apply changes."
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
async pluginsInstall(pathOrSpec, opts = {}) {
|
|
767
|
+
const fileSpec = this.resolveFileNpmSpecToLocalPath(pathOrSpec);
|
|
768
|
+
if (fileSpec && !fileSpec.ok) {
|
|
769
|
+
console.error(fileSpec.error);
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
const normalized = fileSpec && fileSpec.ok ? fileSpec.path : pathOrSpec;
|
|
773
|
+
const resolved = resolve4(expandHome(normalized));
|
|
774
|
+
const config2 = loadConfig();
|
|
775
|
+
if (existsSync3(resolved)) {
|
|
776
|
+
if (opts.link) {
|
|
777
|
+
const probe = await installPluginFromPath({ path: resolved, dryRun: true });
|
|
778
|
+
if (!probe.ok) {
|
|
779
|
+
console.error(probe.error);
|
|
780
|
+
process.exit(1);
|
|
781
|
+
}
|
|
782
|
+
let next3 = addPluginLoadPath(config2, resolved);
|
|
783
|
+
next3 = enablePluginInConfig(next3, probe.pluginId);
|
|
784
|
+
next3 = recordPluginInstall(next3, {
|
|
785
|
+
pluginId: probe.pluginId,
|
|
786
|
+
source: "path",
|
|
787
|
+
sourcePath: resolved,
|
|
788
|
+
installPath: resolved,
|
|
789
|
+
version: probe.version
|
|
790
|
+
});
|
|
791
|
+
saveConfig(next3);
|
|
792
|
+
console.log(`Linked plugin path: ${resolved}`);
|
|
793
|
+
await this.deps.requestRestart({
|
|
794
|
+
reason: `plugin linked: ${probe.pluginId}`,
|
|
795
|
+
manualMessage: "Restart the gateway to load plugins."
|
|
796
|
+
});
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const result2 = await installPluginFromPath({
|
|
800
|
+
path: resolved,
|
|
801
|
+
logger: {
|
|
802
|
+
info: (message) => console.log(message),
|
|
803
|
+
warn: (message) => console.warn(message)
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
if (!result2.ok) {
|
|
807
|
+
console.error(result2.error);
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
let next2 = enablePluginInConfig(config2, result2.pluginId);
|
|
811
|
+
next2 = recordPluginInstall(next2, {
|
|
812
|
+
pluginId: result2.pluginId,
|
|
813
|
+
source: this.isArchivePath(resolved) ? "archive" : "path",
|
|
814
|
+
sourcePath: resolved,
|
|
815
|
+
installPath: result2.targetDir,
|
|
816
|
+
version: result2.version
|
|
817
|
+
});
|
|
818
|
+
saveConfig(next2);
|
|
819
|
+
console.log(`Installed plugin: ${result2.pluginId}`);
|
|
820
|
+
await this.deps.requestRestart({
|
|
821
|
+
reason: `plugin installed: ${result2.pluginId}`,
|
|
822
|
+
manualMessage: "Restart the gateway to load plugins."
|
|
823
|
+
});
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (opts.link) {
|
|
827
|
+
console.error("`--link` requires a local path.");
|
|
828
|
+
process.exit(1);
|
|
829
|
+
}
|
|
830
|
+
if (this.looksLikePath(pathOrSpec)) {
|
|
831
|
+
console.error(`Path not found: ${resolved}`);
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
const result = await installPluginFromNpmSpec({
|
|
835
|
+
spec: pathOrSpec,
|
|
836
|
+
logger: {
|
|
837
|
+
info: (message) => console.log(message),
|
|
838
|
+
warn: (message) => console.warn(message)
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
if (!result.ok) {
|
|
842
|
+
console.error(result.error);
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
let next = enablePluginInConfig(config2, result.pluginId);
|
|
846
|
+
next = recordPluginInstall(next, {
|
|
847
|
+
pluginId: result.pluginId,
|
|
848
|
+
source: "npm",
|
|
849
|
+
spec: pathOrSpec,
|
|
850
|
+
installPath: result.targetDir,
|
|
851
|
+
version: result.version
|
|
852
|
+
});
|
|
853
|
+
saveConfig(next);
|
|
854
|
+
console.log(`Installed plugin: ${result.pluginId}`);
|
|
855
|
+
await this.deps.requestRestart({
|
|
856
|
+
reason: `plugin installed: ${result.pluginId}`,
|
|
857
|
+
manualMessage: "Restart the gateway to load plugins."
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
pluginsDoctor() {
|
|
861
|
+
const config2 = loadConfig();
|
|
862
|
+
const workspaceDir = getWorkspacePath(config2.agents.defaults.workspace);
|
|
863
|
+
const report = buildPluginStatusReport({
|
|
864
|
+
config: config2,
|
|
865
|
+
workspaceDir,
|
|
866
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
867
|
+
reservedProviderIds: PROVIDERS.map((provider) => provider.name)
|
|
868
|
+
});
|
|
869
|
+
const pluginErrors = report.plugins.filter((plugin) => plugin.status === "error");
|
|
870
|
+
const diagnostics = report.diagnostics.filter((diag) => diag.level === "error");
|
|
871
|
+
if (pluginErrors.length === 0 && diagnostics.length === 0) {
|
|
872
|
+
console.log("No plugin issues detected.");
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if (pluginErrors.length > 0) {
|
|
876
|
+
console.log("Plugin errors:");
|
|
877
|
+
for (const entry of pluginErrors) {
|
|
878
|
+
console.log(`- ${entry.id}: ${entry.error ?? "failed to load"} (${entry.source})`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (diagnostics.length > 0) {
|
|
882
|
+
if (pluginErrors.length > 0) {
|
|
883
|
+
console.log("");
|
|
884
|
+
}
|
|
885
|
+
console.log("Diagnostics:");
|
|
886
|
+
for (const diag of diagnostics) {
|
|
887
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
888
|
+
console.log(`- ${prefix}${diag.message}`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
async confirmYesNo(question) {
|
|
893
|
+
const rl = createInterface({
|
|
894
|
+
input: process.stdin,
|
|
895
|
+
output: process.stdout
|
|
896
|
+
});
|
|
897
|
+
const answer = await new Promise((resolve9) => {
|
|
898
|
+
rl.question(`${question} [y/N] `, (line) => resolve9(line));
|
|
899
|
+
});
|
|
900
|
+
rl.close();
|
|
901
|
+
const normalized = answer.trim().toLowerCase();
|
|
902
|
+
return normalized === "y" || normalized === "yes";
|
|
903
|
+
}
|
|
904
|
+
resolveFileNpmSpecToLocalPath(raw) {
|
|
905
|
+
const trimmed = raw.trim();
|
|
906
|
+
if (!trimmed.toLowerCase().startsWith("file:")) {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
const rest = trimmed.slice("file:".length);
|
|
910
|
+
if (!rest) {
|
|
911
|
+
return { ok: false, error: "unsupported file: spec: missing path" };
|
|
912
|
+
}
|
|
913
|
+
if (rest.startsWith("///")) {
|
|
914
|
+
return { ok: true, path: rest.slice(2) };
|
|
915
|
+
}
|
|
916
|
+
if (rest.startsWith("//localhost/")) {
|
|
917
|
+
return { ok: true, path: rest.slice("//localhost".length) };
|
|
918
|
+
}
|
|
919
|
+
if (rest.startsWith("//")) {
|
|
920
|
+
return {
|
|
921
|
+
ok: false,
|
|
922
|
+
error: 'unsupported file: URL host (expected "file:<path>" or "file:///abs/path")'
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
return { ok: true, path: rest };
|
|
926
|
+
}
|
|
927
|
+
looksLikePath(raw) {
|
|
928
|
+
return raw.startsWith(".") || raw.startsWith("~") || raw.startsWith("/") || raw.endsWith(".ts") || raw.endsWith(".js") || raw.endsWith(".mjs") || raw.endsWith(".cjs") || raw.endsWith(".tgz") || raw.endsWith(".tar.gz") || raw.endsWith(".tar") || raw.endsWith(".zip");
|
|
929
|
+
}
|
|
930
|
+
isArchivePath(filePath) {
|
|
931
|
+
const lower = filePath.toLowerCase();
|
|
932
|
+
return lower.endsWith(".zip") || lower.endsWith(".tgz") || lower.endsWith(".tar.gz") || lower.endsWith(".tar");
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
|
|
394
936
|
// src/cli/commands/config.ts
|
|
395
|
-
import { buildReloadPlan, diffConfigPaths, loadConfig, saveConfig } from "@nextclaw/core";
|
|
937
|
+
import { buildReloadPlan, diffConfigPaths, loadConfig as loadConfig2, saveConfig as saveConfig2 } from "@nextclaw/core";
|
|
396
938
|
|
|
397
939
|
// src/cli/config-path.ts
|
|
398
940
|
function isIndexSegment(raw) {
|
|
@@ -587,7 +1129,7 @@ var ConfigCommands = class {
|
|
|
587
1129
|
this.deps = deps;
|
|
588
1130
|
}
|
|
589
1131
|
configGet(pathExpr, opts = {}) {
|
|
590
|
-
const config2 =
|
|
1132
|
+
const config2 = loadConfig2();
|
|
591
1133
|
let parsedPath;
|
|
592
1134
|
try {
|
|
593
1135
|
parsedPath = parseRequiredConfigPath(pathExpr);
|
|
@@ -629,7 +1171,7 @@ var ConfigCommands = class {
|
|
|
629
1171
|
process.exit(1);
|
|
630
1172
|
return;
|
|
631
1173
|
}
|
|
632
|
-
const prevConfig =
|
|
1174
|
+
const prevConfig = loadConfig2();
|
|
633
1175
|
const nextConfig = structuredClone(prevConfig);
|
|
634
1176
|
try {
|
|
635
1177
|
setAtConfigPath(nextConfig, parsedPath, parsedValue);
|
|
@@ -638,7 +1180,7 @@ var ConfigCommands = class {
|
|
|
638
1180
|
process.exit(1);
|
|
639
1181
|
return;
|
|
640
1182
|
}
|
|
641
|
-
|
|
1183
|
+
saveConfig2(nextConfig);
|
|
642
1184
|
await this.requestRestartForConfigDiff({
|
|
643
1185
|
prevConfig,
|
|
644
1186
|
nextConfig,
|
|
@@ -655,7 +1197,7 @@ var ConfigCommands = class {
|
|
|
655
1197
|
process.exit(1);
|
|
656
1198
|
return;
|
|
657
1199
|
}
|
|
658
|
-
const prevConfig =
|
|
1200
|
+
const prevConfig = loadConfig2();
|
|
659
1201
|
const nextConfig = structuredClone(prevConfig);
|
|
660
1202
|
const removed = unsetAtConfigPath(nextConfig, parsedPath);
|
|
661
1203
|
if (!removed) {
|
|
@@ -663,7 +1205,7 @@ var ConfigCommands = class {
|
|
|
663
1205
|
process.exit(1);
|
|
664
1206
|
return;
|
|
665
1207
|
}
|
|
666
|
-
|
|
1208
|
+
saveConfig2(nextConfig);
|
|
667
1209
|
await this.requestRestartForConfigDiff({
|
|
668
1210
|
prevConfig,
|
|
669
1211
|
nextConfig,
|
|
@@ -689,13 +1231,14 @@ var ConfigCommands = class {
|
|
|
689
1231
|
|
|
690
1232
|
// src/cli/commands/channels.ts
|
|
691
1233
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
692
|
-
import { loadConfig as
|
|
1234
|
+
import { getWorkspacePath as getWorkspacePath2, loadConfig as loadConfig3, saveConfig as saveConfig3, PROVIDERS as PROVIDERS2 } from "@nextclaw/core";
|
|
1235
|
+
import { buildPluginStatusReport as buildPluginStatusReport2, enablePluginInConfig as enablePluginInConfig2, getPluginChannelBindings } from "@nextclaw/openclaw-compat";
|
|
693
1236
|
var ChannelCommands = class {
|
|
694
1237
|
constructor(deps) {
|
|
695
1238
|
this.deps = deps;
|
|
696
1239
|
}
|
|
697
1240
|
channelsStatus() {
|
|
698
|
-
const config2 =
|
|
1241
|
+
const config2 = loadConfig3();
|
|
699
1242
|
console.log("Channel Status");
|
|
700
1243
|
console.log(`WhatsApp: ${config2.channels.whatsapp.enabled ? "\u2713" : "\u2717"}`);
|
|
701
1244
|
console.log(`Discord: ${config2.channels.discord.enabled ? "\u2713" : "\u2717"}`);
|
|
@@ -704,6 +1247,21 @@ var ChannelCommands = class {
|
|
|
704
1247
|
console.log(`Telegram: ${config2.channels.telegram.enabled ? "\u2713" : "\u2717"}`);
|
|
705
1248
|
console.log(`Slack: ${config2.channels.slack.enabled ? "\u2713" : "\u2717"}`);
|
|
706
1249
|
console.log(`QQ: ${config2.channels.qq.enabled ? "\u2713" : "\u2717"}`);
|
|
1250
|
+
const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
|
|
1251
|
+
const report = buildPluginStatusReport2({
|
|
1252
|
+
config: config2,
|
|
1253
|
+
workspaceDir,
|
|
1254
|
+
reservedChannelIds: Object.keys(config2.channels),
|
|
1255
|
+
reservedProviderIds: PROVIDERS2.map((provider) => provider.name)
|
|
1256
|
+
});
|
|
1257
|
+
const pluginChannels = report.plugins.filter((plugin) => plugin.status === "loaded" && plugin.channelIds.length > 0);
|
|
1258
|
+
if (pluginChannels.length > 0) {
|
|
1259
|
+
console.log("Plugin Channels:");
|
|
1260
|
+
for (const plugin of pluginChannels) {
|
|
1261
|
+
const channels2 = plugin.channelIds.join(", ");
|
|
1262
|
+
console.log(`- ${channels2} (plugin: ${plugin.id})`);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
707
1265
|
}
|
|
708
1266
|
channelsLogin() {
|
|
709
1267
|
const bridgeDir = this.deps.getBridgeDir();
|
|
@@ -714,6 +1272,62 @@ var ChannelCommands = class {
|
|
|
714
1272
|
console.error(`Bridge failed: ${result.status ?? 1}`);
|
|
715
1273
|
}
|
|
716
1274
|
}
|
|
1275
|
+
async channelsAdd(opts) {
|
|
1276
|
+
const channelId = opts.channel?.trim();
|
|
1277
|
+
if (!channelId) {
|
|
1278
|
+
console.error("--channel is required");
|
|
1279
|
+
process.exit(1);
|
|
1280
|
+
}
|
|
1281
|
+
const config2 = loadConfig3();
|
|
1282
|
+
const workspaceDir = getWorkspacePath2(config2.agents.defaults.workspace);
|
|
1283
|
+
const pluginRegistry = loadPluginRegistry(config2, workspaceDir);
|
|
1284
|
+
const bindings = getPluginChannelBindings(pluginRegistry);
|
|
1285
|
+
const binding = bindings.find((entry) => entry.channelId === channelId || entry.pluginId === channelId);
|
|
1286
|
+
if (!binding) {
|
|
1287
|
+
console.error(`No plugin channel found for: ${channelId}`);
|
|
1288
|
+
process.exit(1);
|
|
1289
|
+
}
|
|
1290
|
+
const setup = binding.channel.setup;
|
|
1291
|
+
if (!setup?.applyAccountConfig) {
|
|
1292
|
+
console.error(`Channel "${binding.channelId}" does not support setup.`);
|
|
1293
|
+
process.exit(1);
|
|
1294
|
+
}
|
|
1295
|
+
const input = {
|
|
1296
|
+
name: opts.name,
|
|
1297
|
+
token: opts.token,
|
|
1298
|
+
code: opts.code,
|
|
1299
|
+
url: opts.url,
|
|
1300
|
+
httpUrl: opts.httpUrl
|
|
1301
|
+
};
|
|
1302
|
+
const currentView = toPluginConfigView(config2, bindings);
|
|
1303
|
+
const accountId = binding.channel.config?.defaultAccountId?.(currentView) ?? "default";
|
|
1304
|
+
const validateError = setup.validateInput?.({
|
|
1305
|
+
cfg: currentView,
|
|
1306
|
+
input,
|
|
1307
|
+
accountId
|
|
1308
|
+
});
|
|
1309
|
+
if (validateError) {
|
|
1310
|
+
console.error(`Channel setup validation failed: ${validateError}`);
|
|
1311
|
+
process.exit(1);
|
|
1312
|
+
}
|
|
1313
|
+
const nextView = setup.applyAccountConfig({
|
|
1314
|
+
cfg: currentView,
|
|
1315
|
+
input,
|
|
1316
|
+
accountId
|
|
1317
|
+
});
|
|
1318
|
+
if (!nextView || typeof nextView !== "object" || Array.isArray(nextView)) {
|
|
1319
|
+
console.error("Channel setup returned invalid config payload.");
|
|
1320
|
+
process.exit(1);
|
|
1321
|
+
}
|
|
1322
|
+
let next = mergePluginConfigView(config2, nextView, bindings);
|
|
1323
|
+
next = enablePluginInConfig2(next, binding.pluginId);
|
|
1324
|
+
saveConfig3(next);
|
|
1325
|
+
console.log(`Configured channel "${binding.channelId}" via plugin "${binding.pluginId}".`);
|
|
1326
|
+
await this.deps.requestRestart({
|
|
1327
|
+
reason: `channel configured via plugin: ${binding.pluginId}`,
|
|
1328
|
+
manualMessage: "Restart the gateway to apply changes."
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
717
1331
|
};
|
|
718
1332
|
|
|
719
1333
|
// src/cli/commands/cron.ts
|
|
@@ -794,15 +1408,15 @@ var CronCommands = class {
|
|
|
794
1408
|
|
|
795
1409
|
// src/cli/commands/diagnostics.ts
|
|
796
1410
|
import { createServer as createNetServer } from "net";
|
|
797
|
-
import { existsSync as
|
|
798
|
-
import { resolve as
|
|
1411
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
1412
|
+
import { resolve as resolve5 } from "path";
|
|
799
1413
|
import {
|
|
800
1414
|
APP_NAME,
|
|
801
1415
|
getConfigPath,
|
|
802
1416
|
getDataDir as getDataDir3,
|
|
803
|
-
getWorkspacePath,
|
|
804
|
-
loadConfig as
|
|
805
|
-
PROVIDERS
|
|
1417
|
+
getWorkspacePath as getWorkspacePath3,
|
|
1418
|
+
loadConfig as loadConfig4,
|
|
1419
|
+
PROVIDERS as PROVIDERS3
|
|
806
1420
|
} from "@nextclaw/core";
|
|
807
1421
|
var DiagnosticsCommands = class {
|
|
808
1422
|
constructor(deps) {
|
|
@@ -963,9 +1577,9 @@ var DiagnosticsCommands = class {
|
|
|
963
1577
|
}
|
|
964
1578
|
async collectRuntimeStatus(params) {
|
|
965
1579
|
const configPath = getConfigPath();
|
|
966
|
-
const config2 =
|
|
967
|
-
const workspacePath =
|
|
968
|
-
const serviceStatePath =
|
|
1580
|
+
const config2 = loadConfig4();
|
|
1581
|
+
const workspacePath = getWorkspacePath3(config2.agents.defaults.workspace);
|
|
1582
|
+
const serviceStatePath = resolve5(getDataDir3(), "run", "service.json");
|
|
969
1583
|
const fixActions = [];
|
|
970
1584
|
let serviceState = readServiceState();
|
|
971
1585
|
if (params.fix && serviceState && !isProcessRunning(serviceState.pid)) {
|
|
@@ -984,7 +1598,7 @@ var DiagnosticsCommands = class {
|
|
|
984
1598
|
const managedHealth = running && managedApiUrl ? await this.probeApiHealth(`${managedApiUrl}/health`) : { state: "unreachable", detail: "service not running" };
|
|
985
1599
|
const configuredHealth = await this.probeApiHealth(`${configuredApiUrl}/health`, 900);
|
|
986
1600
|
const orphanSuspected = !running && configuredHealth.state === "ok";
|
|
987
|
-
const providers =
|
|
1601
|
+
const providers = PROVIDERS3.map((spec) => {
|
|
988
1602
|
const provider = config2.providers[spec.name];
|
|
989
1603
|
if (!provider) {
|
|
990
1604
|
return { name: spec.displayName ?? spec.name, configured: false, detail: "missing config" };
|
|
@@ -1004,11 +1618,11 @@ var DiagnosticsCommands = class {
|
|
|
1004
1618
|
});
|
|
1005
1619
|
const issues = [];
|
|
1006
1620
|
const recommendations = [];
|
|
1007
|
-
if (!
|
|
1621
|
+
if (!existsSync4(configPath)) {
|
|
1008
1622
|
issues.push("Config file is missing.");
|
|
1009
1623
|
recommendations.push(`Run ${APP_NAME} init to create config files.`);
|
|
1010
1624
|
}
|
|
1011
|
-
if (!
|
|
1625
|
+
if (!existsSync4(workspacePath)) {
|
|
1012
1626
|
issues.push("Workspace directory does not exist.");
|
|
1013
1627
|
recommendations.push(`Run ${APP_NAME} init to create workspace templates.`);
|
|
1014
1628
|
}
|
|
@@ -1036,13 +1650,13 @@ var DiagnosticsCommands = class {
|
|
|
1036
1650
|
return {
|
|
1037
1651
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1038
1652
|
configPath,
|
|
1039
|
-
configExists:
|
|
1653
|
+
configExists: existsSync4(configPath),
|
|
1040
1654
|
workspacePath,
|
|
1041
|
-
workspaceExists:
|
|
1655
|
+
workspaceExists: existsSync4(workspacePath),
|
|
1042
1656
|
model: config2.agents.defaults.model,
|
|
1043
1657
|
providers,
|
|
1044
1658
|
serviceStatePath,
|
|
1045
|
-
serviceStateExists:
|
|
1659
|
+
serviceStateExists: existsSync4(serviceStatePath),
|
|
1046
1660
|
fixActions,
|
|
1047
1661
|
process: {
|
|
1048
1662
|
managedByState,
|
|
@@ -1092,7 +1706,7 @@ var DiagnosticsCommands = class {
|
|
|
1092
1706
|
}
|
|
1093
1707
|
}
|
|
1094
1708
|
readLogTail(path, maxLines = 25) {
|
|
1095
|
-
if (!
|
|
1709
|
+
if (!existsSync4(path)) {
|
|
1096
1710
|
return [];
|
|
1097
1711
|
}
|
|
1098
1712
|
try {
|
|
@@ -1106,17 +1720,17 @@ var DiagnosticsCommands = class {
|
|
|
1106
1720
|
}
|
|
1107
1721
|
}
|
|
1108
1722
|
async checkPortAvailability(params) {
|
|
1109
|
-
return await new Promise((
|
|
1723
|
+
return await new Promise((resolve9) => {
|
|
1110
1724
|
const server = createNetServer();
|
|
1111
1725
|
server.once("error", (error) => {
|
|
1112
|
-
|
|
1726
|
+
resolve9({
|
|
1113
1727
|
available: false,
|
|
1114
1728
|
detail: `bind failed on ${params.host}:${params.port} (${String(error)})`
|
|
1115
1729
|
});
|
|
1116
1730
|
});
|
|
1117
1731
|
server.listen(params.port, params.host, () => {
|
|
1118
1732
|
server.close(() => {
|
|
1119
|
-
|
|
1733
|
+
resolve9({
|
|
1120
1734
|
available: true,
|
|
1121
1735
|
detail: `bind ok on ${params.host}:${params.port}`
|
|
1122
1736
|
});
|
|
@@ -1137,24 +1751,31 @@ import {
|
|
|
1137
1751
|
getDataDir as getDataDir4,
|
|
1138
1752
|
getProvider,
|
|
1139
1753
|
getProviderName,
|
|
1140
|
-
getWorkspacePath as
|
|
1754
|
+
getWorkspacePath as getWorkspacePath4,
|
|
1141
1755
|
HeartbeatService,
|
|
1142
1756
|
LiteLLMProvider,
|
|
1143
|
-
loadConfig as
|
|
1757
|
+
loadConfig as loadConfig5,
|
|
1144
1758
|
MessageBus,
|
|
1145
1759
|
ProviderManager,
|
|
1146
|
-
saveConfig as
|
|
1760
|
+
saveConfig as saveConfig4,
|
|
1147
1761
|
SessionManager
|
|
1148
1762
|
} from "@nextclaw/core";
|
|
1763
|
+
import {
|
|
1764
|
+
getPluginChannelBindings as getPluginChannelBindings2,
|
|
1765
|
+
resolvePluginChannelMessageToolHints,
|
|
1766
|
+
setPluginRuntimeBridge,
|
|
1767
|
+
startPluginChannelGateways,
|
|
1768
|
+
stopPluginChannelGateways
|
|
1769
|
+
} from "@nextclaw/openclaw-compat";
|
|
1149
1770
|
import { startUiServer } from "@nextclaw/server";
|
|
1150
1771
|
import { closeSync, mkdirSync as mkdirSync2, openSync } from "fs";
|
|
1151
|
-
import { join as join4, resolve as
|
|
1772
|
+
import { join as join4, resolve as resolve6 } from "path";
|
|
1152
1773
|
import { spawn as spawn2 } from "child_process";
|
|
1153
1774
|
import chokidar from "chokidar";
|
|
1154
1775
|
|
|
1155
1776
|
// src/cli/gateway/controller.ts
|
|
1156
1777
|
import { createHash } from "crypto";
|
|
1157
|
-
import { existsSync as
|
|
1778
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
1158
1779
|
import {
|
|
1159
1780
|
buildConfigSchema,
|
|
1160
1781
|
ConfigSchema,
|
|
@@ -1165,7 +1786,7 @@ var readConfigSnapshot = (getConfigPath4) => {
|
|
|
1165
1786
|
const path = getConfigPath4();
|
|
1166
1787
|
let raw = "";
|
|
1167
1788
|
let parsed = {};
|
|
1168
|
-
if (
|
|
1789
|
+
if (existsSync5(path)) {
|
|
1169
1790
|
raw = readFileSync3(path, "utf-8");
|
|
1170
1791
|
try {
|
|
1171
1792
|
parsed = JSON.parse(raw);
|
|
@@ -1491,8 +2112,11 @@ var ServiceCommands = class {
|
|
|
1491
2112
|
this.deps = deps;
|
|
1492
2113
|
}
|
|
1493
2114
|
async startGateway(options = {}) {
|
|
1494
|
-
const config2 =
|
|
1495
|
-
const workspace =
|
|
2115
|
+
const config2 = loadConfig5();
|
|
2116
|
+
const workspace = getWorkspacePath4(config2.agents.defaults.workspace);
|
|
2117
|
+
const pluginRegistry = loadPluginRegistry(config2, workspace);
|
|
2118
|
+
const extensionRegistry = toExtensionRegistry(pluginRegistry);
|
|
2119
|
+
logPluginDiagnostics(pluginRegistry);
|
|
1496
2120
|
const bus = new MessageBus();
|
|
1497
2121
|
const provider = options.allowMissingProvider === true ? this.makeProvider(config2, { allowMissing: true }) : this.makeProvider(config2);
|
|
1498
2122
|
const providerManager = new ProviderManager(provider ?? this.makeMissingProvider(config2));
|
|
@@ -1504,7 +2128,7 @@ var ServiceCommands = class {
|
|
|
1504
2128
|
if (!provider) {
|
|
1505
2129
|
console.warn("Warning: No API key configured. The gateway is running, but agent replies are disabled until provider config is set.");
|
|
1506
2130
|
}
|
|
1507
|
-
const channels2 = new ChannelManager2(config2, bus, sessionManager,
|
|
2131
|
+
const channels2 = new ChannelManager2(config2, bus, sessionManager, extensionRegistry.channels);
|
|
1508
2132
|
const reloader = new ConfigReloader({
|
|
1509
2133
|
initialConfig: config2,
|
|
1510
2134
|
channels: channels2,
|
|
@@ -1512,7 +2136,8 @@ var ServiceCommands = class {
|
|
|
1512
2136
|
sessionManager,
|
|
1513
2137
|
providerManager,
|
|
1514
2138
|
makeProvider: (nextConfig) => this.makeProvider(nextConfig, { allowMissing: true }) ?? this.makeMissingProvider(nextConfig),
|
|
1515
|
-
loadConfig:
|
|
2139
|
+
loadConfig: loadConfig5,
|
|
2140
|
+
getExtensionChannels: () => extensionRegistry.channels,
|
|
1516
2141
|
onRestartRequired: (paths) => {
|
|
1517
2142
|
void this.deps.requestRestart({
|
|
1518
2143
|
reason: `config reload requires restart: ${paths.join(", ")}`,
|
|
@@ -1525,7 +2150,7 @@ var ServiceCommands = class {
|
|
|
1525
2150
|
reloader,
|
|
1526
2151
|
cron: cron2,
|
|
1527
2152
|
getConfigPath: getConfigPath2,
|
|
1528
|
-
saveConfig:
|
|
2153
|
+
saveConfig: saveConfig4,
|
|
1529
2154
|
requestRestart: async (options2) => {
|
|
1530
2155
|
await this.deps.requestRestart({
|
|
1531
2156
|
reason: options2?.reason ?? "gateway tool restart",
|
|
@@ -1551,9 +2176,55 @@ var ServiceCommands = class {
|
|
|
1551
2176
|
sessionManager,
|
|
1552
2177
|
contextConfig: config2.agents.context,
|
|
1553
2178
|
gatewayController,
|
|
1554
|
-
config: config2
|
|
2179
|
+
config: config2,
|
|
2180
|
+
extensionRegistry,
|
|
2181
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints({
|
|
2182
|
+
registry: pluginRegistry,
|
|
2183
|
+
channel,
|
|
2184
|
+
cfg: loadConfig5(),
|
|
2185
|
+
accountId
|
|
2186
|
+
})
|
|
1555
2187
|
});
|
|
1556
2188
|
reloader.setApplyAgentRuntimeConfig((nextConfig) => agent.applyRuntimeConfig(nextConfig));
|
|
2189
|
+
const pluginChannelBindings = getPluginChannelBindings2(pluginRegistry);
|
|
2190
|
+
setPluginRuntimeBridge({
|
|
2191
|
+
loadConfig: () => toPluginConfigView(loadConfig5(), pluginChannelBindings),
|
|
2192
|
+
writeConfigFile: async (nextConfigView) => {
|
|
2193
|
+
if (!nextConfigView || typeof nextConfigView !== "object" || Array.isArray(nextConfigView)) {
|
|
2194
|
+
throw new Error("plugin runtime writeConfigFile expects an object config");
|
|
2195
|
+
}
|
|
2196
|
+
const current = loadConfig5();
|
|
2197
|
+
const next = mergePluginConfigView(current, nextConfigView, pluginChannelBindings);
|
|
2198
|
+
saveConfig4(next);
|
|
2199
|
+
},
|
|
2200
|
+
dispatchReplyWithBufferedBlockDispatcher: async ({ ctx, dispatcherOptions }) => {
|
|
2201
|
+
const bodyForAgent = typeof ctx.BodyForAgent === "string" ? ctx.BodyForAgent : "";
|
|
2202
|
+
const body = typeof ctx.Body === "string" ? ctx.Body : "";
|
|
2203
|
+
const content = (bodyForAgent || body).trim();
|
|
2204
|
+
if (!content) {
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
const sessionKey = typeof ctx.SessionKey === "string" && ctx.SessionKey.trim().length > 0 ? ctx.SessionKey : `plugin:${typeof ctx.OriginatingChannel === "string" ? ctx.OriginatingChannel : "channel"}:${typeof ctx.SenderId === "string" ? ctx.SenderId : "unknown"}`;
|
|
2208
|
+
const channel = typeof ctx.OriginatingChannel === "string" && ctx.OriginatingChannel.trim().length > 0 ? ctx.OriginatingChannel : "cli";
|
|
2209
|
+
const chatId = typeof ctx.OriginatingTo === "string" && ctx.OriginatingTo.trim().length > 0 ? ctx.OriginatingTo : typeof ctx.SenderId === "string" && ctx.SenderId.trim().length > 0 ? ctx.SenderId : "direct";
|
|
2210
|
+
try {
|
|
2211
|
+
const response = await agent.processDirect({
|
|
2212
|
+
content,
|
|
2213
|
+
sessionKey,
|
|
2214
|
+
channel,
|
|
2215
|
+
chatId,
|
|
2216
|
+
metadata: typeof ctx.AccountId === "string" && ctx.AccountId.trim().length > 0 ? { account_id: ctx.AccountId } : {}
|
|
2217
|
+
});
|
|
2218
|
+
const replyText = typeof response === "string" ? response : String(response ?? "");
|
|
2219
|
+
if (replyText.trim()) {
|
|
2220
|
+
await dispatcherOptions.deliver({ text: replyText }, { kind: "final" });
|
|
2221
|
+
}
|
|
2222
|
+
} catch (error) {
|
|
2223
|
+
dispatcherOptions.onError?.(error);
|
|
2224
|
+
throw error;
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
1557
2228
|
cron2.onJob = async (job) => {
|
|
1558
2229
|
const response = await agent.processDirect({
|
|
1559
2230
|
content: job.payload.message,
|
|
@@ -1599,10 +2270,35 @@ var ServiceCommands = class {
|
|
|
1599
2270
|
watcher.on("unlink", () => reloader.scheduleReload("config unlink"));
|
|
1600
2271
|
await cron2.start();
|
|
1601
2272
|
await heartbeat.start();
|
|
1602
|
-
|
|
2273
|
+
let pluginGatewayHandles = [];
|
|
2274
|
+
try {
|
|
2275
|
+
const startedPluginGateways = await startPluginChannelGateways({
|
|
2276
|
+
registry: pluginRegistry,
|
|
2277
|
+
logger: {
|
|
2278
|
+
info: (message) => console.log(`[plugins] ${message}`),
|
|
2279
|
+
warn: (message) => console.warn(`[plugins] ${message}`),
|
|
2280
|
+
error: (message) => console.error(`[plugins] ${message}`),
|
|
2281
|
+
debug: (message) => console.debug(`[plugins] ${message}`)
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
pluginGatewayHandles = startedPluginGateways.handles;
|
|
2285
|
+
for (const diag of startedPluginGateways.diagnostics) {
|
|
2286
|
+
const prefix = diag.pluginId ? `${diag.pluginId}: ` : "";
|
|
2287
|
+
const text = `${prefix}${diag.message}`;
|
|
2288
|
+
if (diag.level === "error") {
|
|
2289
|
+
console.error(`[plugins] ${text}`);
|
|
2290
|
+
} else {
|
|
2291
|
+
console.warn(`[plugins] ${text}`);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
await Promise.allSettled([agent.run(), reloader.getChannels().startAll()]);
|
|
2295
|
+
} finally {
|
|
2296
|
+
await stopPluginChannelGateways(pluginGatewayHandles);
|
|
2297
|
+
setPluginRuntimeBridge(null);
|
|
2298
|
+
}
|
|
1603
2299
|
}
|
|
1604
2300
|
async runForeground(options) {
|
|
1605
|
-
const config2 =
|
|
2301
|
+
const config2 = loadConfig5();
|
|
1606
2302
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
1607
2303
|
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
1608
2304
|
if (options.open) {
|
|
@@ -1615,7 +2311,7 @@ var ServiceCommands = class {
|
|
|
1615
2311
|
});
|
|
1616
2312
|
}
|
|
1617
2313
|
async startService(options) {
|
|
1618
|
-
const config2 =
|
|
2314
|
+
const config2 = loadConfig5();
|
|
1619
2315
|
const uiConfig = resolveUiConfig(config2, options.uiOverrides);
|
|
1620
2316
|
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
1621
2317
|
const apiUrl = `${uiUrl}/api`;
|
|
@@ -1664,7 +2360,7 @@ var ServiceCommands = class {
|
|
|
1664
2360
|
console.log("Warning: UI frontend not found in package assets.");
|
|
1665
2361
|
}
|
|
1666
2362
|
const logPath = resolveServiceLogPath();
|
|
1667
|
-
const logDir =
|
|
2363
|
+
const logDir = resolve6(logPath, "..");
|
|
1668
2364
|
mkdirSync2(logDir, { recursive: true });
|
|
1669
2365
|
const logFd = openSync(logPath, "a");
|
|
1670
2366
|
const serveArgs = buildServeArgs({
|
|
@@ -1759,22 +2455,22 @@ var ServiceCommands = class {
|
|
|
1759
2455
|
try {
|
|
1760
2456
|
const response = await fetch(params.healthUrl, { method: "GET" });
|
|
1761
2457
|
if (!response.ok) {
|
|
1762
|
-
await new Promise((
|
|
2458
|
+
await new Promise((resolve9) => setTimeout(resolve9, 200));
|
|
1763
2459
|
continue;
|
|
1764
2460
|
}
|
|
1765
2461
|
const payload = await response.json();
|
|
1766
2462
|
const healthy = payload?.ok === true && payload?.data?.status === "ok";
|
|
1767
2463
|
if (!healthy) {
|
|
1768
|
-
await new Promise((
|
|
2464
|
+
await new Promise((resolve9) => setTimeout(resolve9, 200));
|
|
1769
2465
|
continue;
|
|
1770
2466
|
}
|
|
1771
|
-
await new Promise((
|
|
2467
|
+
await new Promise((resolve9) => setTimeout(resolve9, 300));
|
|
1772
2468
|
if (isProcessRunning(params.pid)) {
|
|
1773
2469
|
return true;
|
|
1774
2470
|
}
|
|
1775
2471
|
} catch {
|
|
1776
2472
|
}
|
|
1777
|
-
await new Promise((
|
|
2473
|
+
await new Promise((resolve9) => setTimeout(resolve9, 200));
|
|
1778
2474
|
}
|
|
1779
2475
|
return false;
|
|
1780
2476
|
}
|
|
@@ -1847,9 +2543,9 @@ var ServiceCommands = class {
|
|
|
1847
2543
|
};
|
|
1848
2544
|
|
|
1849
2545
|
// src/cli/workspace.ts
|
|
1850
|
-
import { cpSync, existsSync as
|
|
2546
|
+
import { cpSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1851
2547
|
import { createRequire } from "module";
|
|
1852
|
-
import { dirname, join as join5, resolve as
|
|
2548
|
+
import { dirname, join as join5, resolve as resolve7 } from "path";
|
|
1853
2549
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1854
2550
|
import { APP_NAME as APP_NAME3, getDataDir as getDataDir5 } from "@nextclaw/core";
|
|
1855
2551
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
@@ -1880,11 +2576,11 @@ var WorkspaceManager = class {
|
|
|
1880
2576
|
];
|
|
1881
2577
|
for (const entry of templateFiles) {
|
|
1882
2578
|
const filePath = join5(workspace, entry.target);
|
|
1883
|
-
if (!force &&
|
|
2579
|
+
if (!force && existsSync6(filePath)) {
|
|
1884
2580
|
continue;
|
|
1885
2581
|
}
|
|
1886
2582
|
const templatePath = join5(templateDir, entry.source);
|
|
1887
|
-
if (!
|
|
2583
|
+
if (!existsSync6(templatePath)) {
|
|
1888
2584
|
console.warn(`Warning: Template file missing: ${templatePath}`);
|
|
1889
2585
|
continue;
|
|
1890
2586
|
}
|
|
@@ -1895,12 +2591,12 @@ var WorkspaceManager = class {
|
|
|
1895
2591
|
created.push(entry.target);
|
|
1896
2592
|
}
|
|
1897
2593
|
const memoryDir = join5(workspace, "memory");
|
|
1898
|
-
if (!
|
|
2594
|
+
if (!existsSync6(memoryDir)) {
|
|
1899
2595
|
mkdirSync3(memoryDir, { recursive: true });
|
|
1900
2596
|
created.push(join5("memory", ""));
|
|
1901
2597
|
}
|
|
1902
2598
|
const skillsDir = join5(workspace, "skills");
|
|
1903
|
-
if (!
|
|
2599
|
+
if (!existsSync6(skillsDir)) {
|
|
1904
2600
|
mkdirSync3(skillsDir, { recursive: true });
|
|
1905
2601
|
created.push(join5("skills", ""));
|
|
1906
2602
|
}
|
|
@@ -1922,11 +2618,11 @@ var WorkspaceManager = class {
|
|
|
1922
2618
|
continue;
|
|
1923
2619
|
}
|
|
1924
2620
|
const src = join5(sourceDir, entry.name);
|
|
1925
|
-
if (!
|
|
2621
|
+
if (!existsSync6(join5(src, "SKILL.md"))) {
|
|
1926
2622
|
continue;
|
|
1927
2623
|
}
|
|
1928
2624
|
const dest = join5(targetDir, entry.name);
|
|
1929
|
-
if (!force &&
|
|
2625
|
+
if (!force && existsSync6(dest)) {
|
|
1930
2626
|
continue;
|
|
1931
2627
|
}
|
|
1932
2628
|
cpSync(src, dest, { recursive: true, force: true });
|
|
@@ -1938,13 +2634,13 @@ var WorkspaceManager = class {
|
|
|
1938
2634
|
try {
|
|
1939
2635
|
const require2 = createRequire(import.meta.url);
|
|
1940
2636
|
const entry = require2.resolve("@nextclaw/core");
|
|
1941
|
-
const pkgRoot =
|
|
2637
|
+
const pkgRoot = resolve7(dirname(entry), "..");
|
|
1942
2638
|
const distSkills = join5(pkgRoot, "dist", "skills");
|
|
1943
|
-
if (
|
|
2639
|
+
if (existsSync6(distSkills)) {
|
|
1944
2640
|
return distSkills;
|
|
1945
2641
|
}
|
|
1946
2642
|
const srcSkills = join5(pkgRoot, "src", "agent", "skills");
|
|
1947
|
-
if (
|
|
2643
|
+
if (existsSync6(srcSkills)) {
|
|
1948
2644
|
return srcSkills;
|
|
1949
2645
|
}
|
|
1950
2646
|
return null;
|
|
@@ -1957,11 +2653,11 @@ var WorkspaceManager = class {
|
|
|
1957
2653
|
if (override) {
|
|
1958
2654
|
return override;
|
|
1959
2655
|
}
|
|
1960
|
-
const cliDir =
|
|
1961
|
-
const pkgRoot =
|
|
2656
|
+
const cliDir = resolve7(fileURLToPath2(new URL(".", import.meta.url)));
|
|
2657
|
+
const pkgRoot = resolve7(cliDir, "..", "..");
|
|
1962
2658
|
const candidates = [join5(pkgRoot, "templates")];
|
|
1963
2659
|
for (const candidate of candidates) {
|
|
1964
|
-
if (
|
|
2660
|
+
if (existsSync6(candidate)) {
|
|
1965
2661
|
return candidate;
|
|
1966
2662
|
}
|
|
1967
2663
|
}
|
|
@@ -1969,21 +2665,21 @@ var WorkspaceManager = class {
|
|
|
1969
2665
|
}
|
|
1970
2666
|
getBridgeDir() {
|
|
1971
2667
|
const userBridge = join5(getDataDir5(), "bridge");
|
|
1972
|
-
if (
|
|
2668
|
+
if (existsSync6(join5(userBridge, "dist", "index.js"))) {
|
|
1973
2669
|
return userBridge;
|
|
1974
2670
|
}
|
|
1975
2671
|
if (!which("npm")) {
|
|
1976
2672
|
console.error("npm not found. Please install Node.js >= 18.");
|
|
1977
2673
|
process.exit(1);
|
|
1978
2674
|
}
|
|
1979
|
-
const cliDir =
|
|
1980
|
-
const pkgRoot =
|
|
2675
|
+
const cliDir = resolve7(fileURLToPath2(new URL(".", import.meta.url)));
|
|
2676
|
+
const pkgRoot = resolve7(cliDir, "..", "..");
|
|
1981
2677
|
const pkgBridge = join5(pkgRoot, "bridge");
|
|
1982
2678
|
const srcBridge = join5(pkgRoot, "..", "..", "bridge");
|
|
1983
2679
|
let source = null;
|
|
1984
|
-
if (
|
|
2680
|
+
if (existsSync6(join5(pkgBridge, "package.json"))) {
|
|
1985
2681
|
source = pkgBridge;
|
|
1986
|
-
} else if (
|
|
2682
|
+
} else if (existsSync6(join5(srcBridge, "package.json"))) {
|
|
1987
2683
|
source = srcBridge;
|
|
1988
2684
|
}
|
|
1989
2685
|
if (!source) {
|
|
@@ -1991,8 +2687,8 @@ var WorkspaceManager = class {
|
|
|
1991
2687
|
process.exit(1);
|
|
1992
2688
|
}
|
|
1993
2689
|
console.log(`${this.logo} Setting up bridge...`);
|
|
1994
|
-
mkdirSync3(
|
|
1995
|
-
if (
|
|
2690
|
+
mkdirSync3(resolve7(userBridge, ".."), { recursive: true });
|
|
2691
|
+
if (existsSync6(userBridge)) {
|
|
1996
2692
|
rmSync2(userBridge, { recursive: true, force: true });
|
|
1997
2693
|
}
|
|
1998
2694
|
cpSync(source, userBridge, {
|
|
@@ -2032,6 +2728,7 @@ var CliRuntime = class {
|
|
|
2032
2728
|
workspaceManager;
|
|
2033
2729
|
serviceCommands;
|
|
2034
2730
|
configCommands;
|
|
2731
|
+
pluginCommands;
|
|
2035
2732
|
channelCommands;
|
|
2036
2733
|
cronCommands;
|
|
2037
2734
|
diagnosticsCommands;
|
|
@@ -2044,9 +2741,13 @@ var CliRuntime = class {
|
|
|
2044
2741
|
this.configCommands = new ConfigCommands({
|
|
2045
2742
|
requestRestart: (params) => this.requestRestart(params)
|
|
2046
2743
|
});
|
|
2744
|
+
this.pluginCommands = new PluginCommands({
|
|
2745
|
+
requestRestart: (params) => this.requestRestart(params)
|
|
2746
|
+
});
|
|
2047
2747
|
this.channelCommands = new ChannelCommands({
|
|
2048
2748
|
logo: this.logo,
|
|
2049
|
-
getBridgeDir: () => this.workspaceManager.getBridgeDir()
|
|
2749
|
+
getBridgeDir: () => this.workspaceManager.getBridgeDir(),
|
|
2750
|
+
requestRestart: (params) => this.requestRestart(params)
|
|
2050
2751
|
});
|
|
2051
2752
|
this.cronCommands = new CronCommands();
|
|
2052
2753
|
this.diagnosticsCommands = new DiagnosticsCommands({ logo: this.logo });
|
|
@@ -2112,7 +2813,7 @@ var CliRuntime = class {
|
|
|
2112
2813
|
const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? Math.max(0, Math.floor(params.delayMs)) : 100;
|
|
2113
2814
|
const cliPath = process.env.NEXTCLAW_SELF_RELAUNCH_CLI?.trim() || fileURLToPath3(new URL("./index.js", import.meta.url));
|
|
2114
2815
|
const startArgs = [cliPath, "start", "--ui-port", String(uiPort)];
|
|
2115
|
-
const serviceStatePath =
|
|
2816
|
+
const serviceStatePath = resolve8(getDataDir6(), "run", "service.json");
|
|
2116
2817
|
const helperScript = [
|
|
2117
2818
|
'const { spawnSync } = require("node:child_process");',
|
|
2118
2819
|
'const { readFileSync } = require("node:fs");',
|
|
@@ -2217,15 +2918,15 @@ var CliRuntime = class {
|
|
|
2217
2918
|
const force = Boolean(options.force);
|
|
2218
2919
|
const configPath = getConfigPath3();
|
|
2219
2920
|
let createdConfig = false;
|
|
2220
|
-
if (!
|
|
2921
|
+
if (!existsSync7(configPath)) {
|
|
2221
2922
|
const config3 = ConfigSchema2.parse({});
|
|
2222
|
-
|
|
2923
|
+
saveConfig5(config3);
|
|
2223
2924
|
createdConfig = true;
|
|
2224
2925
|
}
|
|
2225
|
-
const config2 =
|
|
2926
|
+
const config2 = loadConfig6();
|
|
2226
2927
|
const workspaceSetting = config2.agents.defaults.workspace;
|
|
2227
|
-
const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join6(getDataDir6(), DEFAULT_WORKSPACE_DIR) :
|
|
2228
|
-
const workspaceExisted =
|
|
2928
|
+
const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join6(getDataDir6(), DEFAULT_WORKSPACE_DIR) : expandHome2(workspaceSetting);
|
|
2929
|
+
const workspaceExisted = existsSync7(workspacePath);
|
|
2229
2930
|
mkdirSync4(workspacePath, { recursive: true });
|
|
2230
2931
|
const templateResult = this.workspaceManager.createWorkspaceTemplates(workspacePath, { force });
|
|
2231
2932
|
if (createdConfig) {
|
|
@@ -2322,8 +3023,11 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
2322
3023
|
await this.serviceCommands.stopService();
|
|
2323
3024
|
}
|
|
2324
3025
|
async agent(opts) {
|
|
2325
|
-
const config2 =
|
|
2326
|
-
const workspace =
|
|
3026
|
+
const config2 = loadConfig6();
|
|
3027
|
+
const workspace = getWorkspacePath5(config2.agents.defaults.workspace);
|
|
3028
|
+
const pluginRegistry = loadPluginRegistry(config2, workspace);
|
|
3029
|
+
const extensionRegistry = toExtensionRegistry(pluginRegistry);
|
|
3030
|
+
logPluginDiagnostics(pluginRegistry);
|
|
2327
3031
|
const bus = new MessageBus2();
|
|
2328
3032
|
const provider = this.serviceCommands.createProvider(config2) ?? this.serviceCommands.createMissingProvider(config2);
|
|
2329
3033
|
const providerManager = new ProviderManager2(provider);
|
|
@@ -2339,7 +3043,14 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
2339
3043
|
execConfig: config2.tools.exec,
|
|
2340
3044
|
restrictToWorkspace: config2.tools.restrictToWorkspace,
|
|
2341
3045
|
contextConfig: config2.agents.context,
|
|
2342
|
-
config: config2
|
|
3046
|
+
config: config2,
|
|
3047
|
+
extensionRegistry,
|
|
3048
|
+
resolveMessageToolHints: ({ channel, accountId }) => resolvePluginChannelMessageToolHints2({
|
|
3049
|
+
registry: pluginRegistry,
|
|
3050
|
+
channel,
|
|
3051
|
+
cfg: loadConfig6(),
|
|
3052
|
+
accountId
|
|
3053
|
+
})
|
|
2343
3054
|
});
|
|
2344
3055
|
if (opts.message) {
|
|
2345
3056
|
const response = await agentLoop.processDirect({
|
|
@@ -2354,10 +3065,10 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
2354
3065
|
console.log(`${this.logo} Interactive mode (type exit or Ctrl+C to quit)
|
|
2355
3066
|
`);
|
|
2356
3067
|
const historyFile = join6(getDataDir6(), "history", "cli_history");
|
|
2357
|
-
const historyDir =
|
|
3068
|
+
const historyDir = resolve8(historyFile, "..");
|
|
2358
3069
|
mkdirSync4(historyDir, { recursive: true });
|
|
2359
|
-
const history =
|
|
2360
|
-
const rl =
|
|
3070
|
+
const history = existsSync7(historyFile) ? readFileSync5(historyFile, "utf-8").split("\n").filter(Boolean) : [];
|
|
3071
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
2361
3072
|
rl.on("close", () => {
|
|
2362
3073
|
const merged = history.concat(rl.history ?? []);
|
|
2363
3074
|
writeFileSync3(historyFile, merged.join("\n"));
|
|
@@ -2417,6 +3128,27 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
2417
3128
|
console.log(`Tip: restart ${APP_NAME4} to apply the update.`);
|
|
2418
3129
|
}
|
|
2419
3130
|
}
|
|
3131
|
+
pluginsList(opts = {}) {
|
|
3132
|
+
this.pluginCommands.pluginsList(opts);
|
|
3133
|
+
}
|
|
3134
|
+
pluginsInfo(id, opts = {}) {
|
|
3135
|
+
this.pluginCommands.pluginsInfo(id, opts);
|
|
3136
|
+
}
|
|
3137
|
+
async pluginsEnable(id) {
|
|
3138
|
+
await this.pluginCommands.pluginsEnable(id);
|
|
3139
|
+
}
|
|
3140
|
+
async pluginsDisable(id) {
|
|
3141
|
+
await this.pluginCommands.pluginsDisable(id);
|
|
3142
|
+
}
|
|
3143
|
+
async pluginsUninstall(id, opts = {}) {
|
|
3144
|
+
await this.pluginCommands.pluginsUninstall(id, opts);
|
|
3145
|
+
}
|
|
3146
|
+
async pluginsInstall(pathOrSpec, opts = {}) {
|
|
3147
|
+
await this.pluginCommands.pluginsInstall(pathOrSpec, opts);
|
|
3148
|
+
}
|
|
3149
|
+
pluginsDoctor() {
|
|
3150
|
+
this.pluginCommands.pluginsDoctor();
|
|
3151
|
+
}
|
|
2420
3152
|
configGet(pathExpr, opts = {}) {
|
|
2421
3153
|
this.configCommands.configGet(pathExpr, opts);
|
|
2422
3154
|
}
|
|
@@ -2432,6 +3164,9 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
2432
3164
|
channelsLogin() {
|
|
2433
3165
|
this.channelCommands.channelsLogin();
|
|
2434
3166
|
}
|
|
3167
|
+
async channelsAdd(opts) {
|
|
3168
|
+
await this.channelCommands.channelsAdd(opts);
|
|
3169
|
+
}
|
|
2435
3170
|
cronList(opts) {
|
|
2436
3171
|
this.cronCommands.cronList(opts);
|
|
2437
3172
|
}
|
|
@@ -2454,7 +3189,7 @@ ${this.logo} ${APP_NAME4} is ready! (${source})`);
|
|
|
2454
3189
|
await this.diagnosticsCommands.doctor(opts);
|
|
2455
3190
|
}
|
|
2456
3191
|
async skillsInstall(options) {
|
|
2457
|
-
const workdir = options.workdir ?
|
|
3192
|
+
const workdir = options.workdir ? expandHome2(options.workdir) : getWorkspacePath5();
|
|
2458
3193
|
const result = await installClawHubSkill({
|
|
2459
3194
|
slug: options.slug,
|
|
2460
3195
|
version: options.version,
|
|
@@ -2497,11 +3232,20 @@ var skills = program.command("skills").description("Manage skills");
|
|
|
2497
3232
|
registerClawHubInstall(skills);
|
|
2498
3233
|
var clawhub = program.command("clawhub").description("Install skills from ClawHub");
|
|
2499
3234
|
registerClawHubInstall(clawhub);
|
|
3235
|
+
var plugins = program.command("plugins").description("Manage OpenClaw-compatible plugins");
|
|
3236
|
+
plugins.command("list").description("List discovered plugins").option("--json", "Print JSON").option("--enabled", "Only show enabled plugins", false).option("--verbose", "Show detailed entries", false).action((opts) => runtime.pluginsList(opts));
|
|
3237
|
+
plugins.command("info <id>").description("Show plugin details").option("--json", "Print JSON").action((id, opts) => runtime.pluginsInfo(id, opts));
|
|
3238
|
+
plugins.command("enable <id>").description("Enable a plugin in config").action((id) => runtime.pluginsEnable(id));
|
|
3239
|
+
plugins.command("disable <id>").description("Disable a plugin in config").action((id) => runtime.pluginsDisable(id));
|
|
3240
|
+
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));
|
|
3241
|
+
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));
|
|
3242
|
+
plugins.command("doctor").description("Report plugin load issues").action(() => runtime.pluginsDoctor());
|
|
2500
3243
|
var config = program.command("config").description("Manage config values");
|
|
2501
3244
|
config.command("get <path>").description("Get a config value by dot path").option("--json", "Output JSON", false).action((path, opts) => runtime.configGet(path, opts));
|
|
2502
3245
|
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));
|
|
2503
3246
|
config.command("unset <path>").description("Remove a config value by dot path").action((path) => runtime.configUnset(path));
|
|
2504
3247
|
var channels = program.command("channels").description("Manage channels");
|
|
3248
|
+
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));
|
|
2505
3249
|
channels.command("status").description("Show channel status").action(() => runtime.channelsStatus());
|
|
2506
3250
|
channels.command("login").description("Link device via QR code").action(() => runtime.channelsLogin());
|
|
2507
3251
|
var cron = program.command("cron").description("Manage scheduled tasks");
|