Add grouped Claw schema and read-only add plan (#101328)

* Add grouped Claw schema and read-only add plan

* test(claws): cover grouped schema and preview

* docs(claws): document experimental preview

* fix(claws): harden preview consent

* fix(claws): satisfy tool filter lint

* fix(claws): bind plans to validated sources
This commit is contained in:
Gio Della-Libera
2026-07-21 14:50:36 -07:00
committed by GitHub
parent b36342ee6f
commit 138d2a5627
25 changed files with 3462 additions and 12 deletions

View File

@@ -64,6 +64,11 @@ const coreEntrySpecs: readonly CommandGroupDescriptorSpec<
loadModule: () => import("../config-cli.js"),
exportName: "registerConfigCli",
},
{
commandNames: ["claws"],
loadModule: () => import("../claws-cli.js"),
exportName: "registerClawsCli",
},
{
commandNames: ["backup"],
loadModule: () => import("./register.backup.js"),
@@ -135,14 +140,15 @@ const coreEntrySpecs: readonly CommandGroupDescriptorSpec<
];
function resolveCoreCommandGroups(ctx: ProgramContext, argv: string[]): CommandGroupEntry[] {
// Descriptor metadata and import specs stay separate so help can stay cheap.
return buildCommandGroupEntries(
getCoreCliCommandDescriptors(),
coreEntrySpecs,
(register) => async (program) => {
await register({ program, ctx, argv });
},
const descriptors = getCoreCliCommandDescriptors();
const visibleCommandNames = new Set(descriptors.map((descriptor) => descriptor.name));
const visibleEntrySpecs = coreEntrySpecs.filter((spec) =>
spec.commandNames.every((name) => visibleCommandNames.has(name)),
);
// Descriptor metadata and import specs stay separate so help can stay cheap.
return buildCommandGroupEntries(descriptors, visibleEntrySpecs, (register) => async (program) => {
await register({ program, ctx, argv });
});
}
export function getCoreCliCommandNames(): string[] {

View File

@@ -88,6 +88,18 @@ describe("command-registry", () => {
expect(names).toContain("agents");
});
it("only exposes Claws after an explicit process opt-in", () => {
vi.stubEnv("OPENCLAW_EXPERIMENTAL_CLAWS", "");
expect(getCoreCliCommandNames()).not.toContain("claws");
expect(getCoreCliCommandsWithSubcommands()).not.toContain("claws");
vi.stubEnv("OPENCLAW_EXPERIMENTAL_CLAWS", "1");
expect(getCoreCliCommandNames()).toContain("claws");
expect(getCoreCliCommandsWithSubcommands()).toContain("claws");
vi.unstubAllEnvs();
});
it("returns only commands that support subcommands", () => {
const names = getCoreCliCommandsWithSubcommands();
expect(names).toContain("config");

View File

@@ -1,4 +1,5 @@
// Core root-command descriptor catalog used for help placeholders and lazy registration.
import { isExperimentalClawsEnabled } from "../../claws/experimental.js";
import { defineCommandDescriptorCatalog } from "./command-descriptor-utils.js";
import type { NamedCommandDescriptor } from "./command-group-descriptors.js";
@@ -33,6 +34,12 @@ const coreCliCommandCatalog = defineCommandDescriptorCatalog([
"Non-interactive config helpers (get/set/patch/unset/file/schema/validate). Run without subcommand for guided setup.",
hasSubcommands: true,
},
{
name: "claws",
description: "Inspect and preview OpenClaw Claws",
hasSubcommands: true,
parentDefaultHelp: true,
},
{
name: "backup",
description: "Create and verify backup archives and SQLite snapshots",
@@ -124,22 +131,32 @@ const coreCliCommandCatalog = defineCommandDescriptorCatalog([
/** Static root-command descriptors for the core CLI surface. */
export const CORE_CLI_COMMAND_DESCRIPTORS = coreCliCommandCatalog.descriptors;
function visibleCoreCliCommandDescriptors(): ReadonlyArray<CoreCliCommandDescriptor> {
return isExperimentalClawsEnabled()
? CORE_CLI_COMMAND_DESCRIPTORS
: CORE_CLI_COMMAND_DESCRIPTORS.filter((descriptor) => descriptor.name !== "claws");
}
/** Return core root-command descriptors in help/registration order. */
export function getCoreCliCommandDescriptors(): ReadonlyArray<CoreCliCommandDescriptor> {
return coreCliCommandCatalog.getDescriptors();
return visibleCoreCliCommandDescriptors();
}
/** Return names for all core root commands. */
export function getCoreCliCommandNames(): string[] {
return coreCliCommandCatalog.getNames();
return visibleCoreCliCommandDescriptors().map((descriptor) => descriptor.name);
}
/** Return core root commands that own child subcommands. */
export function getCoreCliCommandsWithSubcommands(): string[] {
return coreCliCommandCatalog.getCommandsWithSubcommands();
return visibleCoreCliCommandDescriptors()
.filter((descriptor) => descriptor.hasSubcommands)
.map((descriptor) => descriptor.name);
}
/** Return core root commands whose parent action should default to help. */
export function getCoreCliParentDefaultHelpCommands(): string[] {
return coreCliCommandCatalog.getParentDefaultHelpCommands();
return visibleCoreCliCommandDescriptors()
.filter((descriptor) => descriptor.parentDefaultHelp)
.map((descriptor) => descriptor.name);
}