SuperAPI AI Directory

create-bk-cli-system

Coding & development

View repository

Updated 10-16 08:54

About this skill

Extending bk-cli System

When to Use

This skill uniformly covers two types of work:

  • Adding a new top-level system command
  • Adding one or more actions to an existing system
  • Adding a subsystem layer to an existing system, and adding YAML or Go actions under the subsystem

This skill also provides a summary of shared contracts that must be checked when creating/extending a system; for detailed rules, refer to docs/design.md . First determine whether the system already exists, then decide which branch to take; do not write code first and then go back to add registration or structure.

Recommended reading order

  1. First read AGENTS.md and docs/design.md, to confirm the repository boundaries and design baseline.
  2. Then read the "Must-check summary for extending system" in this skill to confirm the shared rules most likely to cause pitfalls when extending a system; for detailed contracts, always go back to docs/design.md。
  3. Finally, follow this skill's branching process to determine whether it is "create a new system" or "add an action to an existing system".

Step zero: first determine whether a subsystem is needed

If the user's requirements, OpenAPI list, resource tags, or business description naturally divide into multiple modules, pause implementation first and give the user two options:

  1. Flat action: continue using bk-cli <system> <action>, distinguishing modules by action name, for example get_pipeline_build_list。
  2. One subsystem layer: use bk-cli <system> <subsystem> <action>, for example bk-cli devops pipeline get_build_list、bk-cli devops codecc get_task_detail、bk-cli devops stream trigger。

Currently only one subsystem layer is allowed; bk-cli <system> <subsystem> <sub_subsystem> <action>is not allowed. If the user needs deeper levels, stop implementation and provide a public contract change proposal suggestion.

Step one: first determine whether the system already exists

Check in the following order:

  1. Read AGENTS.md、docs/design.md, and first read through the "Must-check summary for extending system" later in this skill
  2. View cmd/system/register.go, to confirm systemCatalog() 's registration method
  3. Check whether the following files already exist:
    • cmd/system/<system>.go
    • cmd/system/<system>/spec.go
    • cmd/system/<system>/actions.yaml
    • cmd/system/<system>/<subsystem>/spec.go
    • cmd/system/<system>/<subsystem>/actions.yaml
  4. Search new<System>SystemSpec() or NewSystemSpec(), to confirm whether the system has already been integrated into the catalog

Determine the result:

  • The system does not exist: first create the system, then add YAML actions, Go-implemented actions, tests, and documentation as needed
  • The system already exists: only add actions, and decide whether to use YAML or Go at the action granularity; do not migrate existing YAML actions for the sake of one Go action
  • The system already exists but the subsystem does not exist: follow "Branch C: Add subsystem" to create the subsystem, then decide YAML or Go at the action granularity

Must-check summary for extending system

The documentation layering is based on AGENTS.md as the standard; for detailed shared contracts, refer to docs/design.md as the standard. When generating system/action, at least first confirm:

  • Context represents an independent BlueKing deployment target, not an API Gateway stage; for detailed context rules, refer to docs/design.md as the standard.
  • --stage、timeout、tenant、--header Do not rewrite the priority yourself; when these behaviors are involved, go back and check docs/design.md。
  • Even if the user uses --header to override X-Bkapi-Authorization,--dry-run / --verbose Sensitive authentication content must still be displayed in a redacted form.
  • YAML action must explicitly declare authConfig;resourcePermissionRequired: true must be set at the same time appVerifiedRequired: true。
  • YAML params only supports in: path、in: query and for help purposes in: header; do not declare in: body。
  • YAML actions uniformly receive shared input:--stage、--body '<json>', duplicate --header 'Key:Value'; when flag name conflicts are retained, the action should be skipped and a warning given.
  • If the OpenAPI request body is very complex and requires the Agent or caller to construct the complete JSON themselves, do not force the body fields to be split into command flags; continue to use the shared --body '<json>', put the request body example in examples, and configure in the YAML action body_schema. If OpenAPI marks the request body as required, also configure body_required: true. By default, help is displayed in the order of Usage、Examples, schema viewing hints, and the complete schema is viewed via bk-cli <system> [subsystem] <action> -h --body-schema .
  • Path placeholder values are escaped as a single URL path segment, and you cannot rely on it to inject / or ?。
  • Go-implemented action must go through systemcmd.ResolveRuntime(deps) and systemcmd.ExecuteRequest(...) or syslib.ExecuteRequest(...) to use the shared execution path, and must not bypass the runtime / output / credential logic.
  • When a Go-implemented action supports both named flags and raw --body , treat --body as an explicit override.

command group form

A command group can be one of the following forms:

  • YAML-driven: all actions are defined by actions.yaml Definition
  • Go-implemented: all actions are implemented by Go code
  • mixed: both YAML actions and Go-implemented actions are retained
  • group-only: the parent system serves only as a grouping, with subsystems attached under it
  • parent actions + subsystems: the parent system has its own actions and also has subsystems attached under it

The implementation method of an action depends only on its own complexity, not on what other actions already use.

Direct subcommand names under the same parent must be unique. A parent system's action names must not conflict with subsystem names.

action selection rules

Prefer the simplest implementation that meets the requirements.

Prefer YAML if and only if all of the following conditions are met:

  • essentially just cli args -> 一次 API 调用
  • no local orchestration logic is needed
  • no local-specific flags are needed, and API request parameters are directly converted into flags
  • no customized return structure is needed
  • Even if there is a request body, as long as the caller can directly provide the complete JSON via --body and examples / body_schema is sufficient to guide the Agent in constructing the body, also prefer to keep it YAML-driven

A Go-implemented action must be used if any one of the following conditions is met:

  • local validation, branching, orchestration, or pagination aggregation is needed
  • zero, one, or multiple API calls are needed
  • local-specific flags are needed, for example --bk_biz_id、--fields、--limit, encapsulated/processed/orchestrated and then used as request parameters
  • the body needs to be synthesized by the CLI from named flags before the request, or local-specific validation/default value handling is needed for body fields
  • after the request, use mutate or a manual envelope to adjust the return

YAML body schema / example rules

body_schema Used for scenarios where "the body is complex, but the action itself is still just one API call". A typical example is when the OpenAPI request body has a large number of nested fields, arrays, or objects, and the Agent needs to construct the complete JSON itself based on the schema.

Rules:

  • Do not generate a large number of command flags for complex body fields; otherwise help will bloat, and the invocation contract will easily drift from OpenAPI.
  • A YAML action only converts path / query parameters into flags; the body continues to be input through the shared --body '<json>' input.
  • body_schema Put the simplified JSON schema or field structure description; a JSON example that can be used directly as a --body starting point goes in the action examples , to avoid maintaining it redundantly with examples.
  • If the OpenAPI request body is required, configure body_required: true; this way, when --body is missing during execution, it will fail locally and will not send an empty body upstream.
  • By default, bk-cli <system> [subsystem] <action> --help must first display Usage and Examples, then display the schema viewing hint;body_schema must be viewable through bk-cli <system> [subsystem] <action> -h --body-schema . System-specific skills/*/SKILL.md can only contain common examples and cannot be the sole source of body structure.
  • --body-schema is a help modifier, not an execution parameter; when used alone without -h , it must fail fast and must not enter the authentication or request execution path.
  • Only when the user explicitly needs friendlier local flags, the CLI needs to synthesize a body, or local validation of complex fields is needed, should this action be made into a separate Go-implemented wrapper.

Core types

systemcmd.SystemSpec

type SystemSpec struct {
    Name              string
    Description       string
    YAMLFile          string
    RegisterGoActions RegisterGoActionsFunc
    Subsystems        []SystemSpec
}

systemcmd.BuildDeps

type BuildDeps struct {
    GetContext func() string
    IsDryRun   func() bool
    IsVerbose  func() bool
    WarnWriter io.Writer
}

syslib.RequestSpec

A Go-implemented action uses it to describe a single request. Common fields:

  • GatewayName
  • Method
  • Path
  • ParamsJSON
  • BodyJSON
  • Headers
  • Stage
  • Timeout
  • AuthConfig

AuthConfig must be set explicitly, using &syslib.AuthConfig{...}。

Shared helper map

Runtime and request execution

Helper Purpose
systemcmd.ResolveRuntime(deps) At the beginning of RunE uniformly parse context, dry-run, verbose, insecure
systemcmd.ExecuteRequest(cmd, runtime, actionName, spec, mutate) Standard execution path for a single-request action
syslib.ExecuteRequest(runtime, spec) Multi-request orchestration, pagination aggregation
systemcmd.EnsureEnvelope(actionName, env) Defend against empty envelope

Flags, validation, and serialization

Helper Purpose
systemcmd.AddCommonRequestFlags(cmd, &stage, &body, &headers) Register --stage、--body、--header
systemcmd.AddCommonRequestFlagsWithoutBody(cmd, &stage, &headers) Register --stage、--header
systemcmd.MarshalJSON(payload) Uniformly serialize body
systemcmd.ValidatePositiveIntFlag(...) Validate required positive integer
systemcmd.ValidatePositiveIntFlagIfChanged(...) Validate optional positive integer
systemcmd.ValidateNonNegativeIntFlag(...) Validate non-negative integer
systemcmd.ValidateNonEmptyStringFlag(...) Validate non-empty string
systemcmd.ParseJSONObjectFlag(flagName, raw) Parse JSON object type flag

Test helper

Helper Purpose
testutil.BuildDeps(dryRun bool) Construct test dependencies
testutil.SetupTestContext(baseURL string) Create default context and credentials
testutil.CaptureCommandStdout(fn) Capture stdout
testutil.BuildYAMLActionCmd(...) Construct YAML action test command

Do not reimplement these shared capabilities yourself.

Branch A: system does not exist, create system first

Files that must be added or updated

Always required:

File Purpose
cmd/system/<system>.go Thin wrapper, calls <system>.NewSystemSpec()
cmd/system/<system>/spec.go NewSystemSpec() implementation
cmd/system/register.go Register in systemCatalog() register in

Add as needed:

File Condition
cmd/system/<system>/actions.yaml This system needs YAML actions
cmd/system/<system>/<action>.go This system needs Go-implemented actions
cmd/system/<system>/common.go Multiple Go actions share logic
cmd/system/<system>/<system>_suite_test.go This system has Ginkgo tests
skills/bk-cli-<system>/SKILL.md When adding a public system, this must be completed, and the content must be in Chinese

Thin wrapper template

package system

import <system>system "github.com/TencentBlueKing/bk-cli/cmd/system/<system>"

func new<System>SystemSpec() SystemSpec {
    return <system>system.NewSystemSpec()
}

spec.go template

YAML-driven

func NewSystemSpec() systemcmd.SystemSpec {
    return systemcmd.SystemSpec{
        Name:        "<system>",
        Description: "<system> system commands",
        YAMLFile:    "<system>/actions.yaml",
    }
}

Go-implemented

func NewSystemSpec() systemcmd.SystemSpec {
    return systemcmd.SystemSpec{
        Name:        "<system>",
        Description: "<system> system commands",
        RegisterGoActions: func(parent *cobra.Command, deps systemcmd.BuildDeps) error {
            parent.AddCommand(newSomeActionCmd(deps))
            return nil
        },
    }
}

mixed

func NewSystemSpec() systemcmd.SystemSpec {
    return systemcmd.SystemSpec{
        Name:        "<system>",
        Description: "<system> system commands",
        YAMLFile:    "<system>/actions.yaml",
        RegisterGoActions: func(parent *cobra.Command, deps systemcmd.BuildDeps) error {
            builders := []func(systemcmd.BuildDeps) *cobra.Command{
                newActionOneCmd,
                newActionTwoCmd,
            }
            for _, build := range builders {
                parent.AddCommand(build(deps))
            }
            return nil
        },
    }
}

Registration and embed constraints

In cmd/system/register.go of systemCatalog() add a new system.

If YAML is used, the file must be placed in cmd/system/<system>/actions.yaml, because the repository relies on //go:embed */actions.yaml for automatic embedding.

common.go recommendation

When multiple Go-implemented actions share logic, put the following content into common.go:

  • Gateway name constants
  • Shared types
  • body builder
  • Shared validation functions
  • factory function

Ginkgo suite

As long as this system has test files, add the suite file:

package <system>_test

import (
    "testing"

    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
)

func TestSuite(t *testing.T) {
    RegisterFailHandler(Fail)
    RunSpecs(t, "<System> Suite")
}

Branch B: system already exists, add a new action

YAML action workflow

The YAML file location is fixed as:

cmd/system/<system>/actions.yaml

If SystemSpec.YAMLFile is already configured, you only need to append the action to the actions list; no additional Go wiring is required.

YAML top-level structure

name: <system>
gateway_name: bk-<upstream>
description: "System description"
actions:
  - ...

YAML action fields

Field Required Description
name Yes action command name
description Yes Cobra Short
method Yes HTTP method
path Yes API path, may include {param}
timeout No Override context timeout, for example 30s
authConfig Yes Authentication configuration
params No Parameter list
examples No Command example
body_schema No Schema or field structure description for a complex request body; through -h --body-schema help the Agent construct --body
body_required No Whether a non-empty --bodyis required at execution time; should be set to true when OpenAPI requestBody.required=true

authConfig

Field Required Description
appVerifiedRequired Yes Whether application authentication is required
userVerifiedRequired Yes Whether user authentication is required
resourcePermissionRequired Yes Whether resource permission validation is required

Constraints:

  • resourcePermissionRequired: true WhenappVerifiedRequired must also be true
  • If neither app nor user is required, the CLI will not generate X-Bkapi-Authorization
  • Even if the user uses --header to override the authentication header, dry-run / verbose must continue to display authentication content in redacted form

params

Field Required Description
name Yes Parameter name, which is also the flag name
in Yes path、query or header
type Yes string、bool、int
description No Help text
required No Whether required
default No Default value

Rules:

  • path and query will generate CLI flags
  • header is only used for help text and does not generate a separate flag
  • Do not write in: body
  • action additionally shares --stage、--body '<json>' and duplicate --header 'Key:Value'; when there is body_schema also supports help modifier --body-schema; when body_required: true is present, --body is a required execution item
  • param names must not conflict with reserved flags:body、body-schema、header、stage、help、context、dry-run、format、verbose、insecure
  • Within the same action, path and query param names must not be duplicated

Go-implemented action workflow

Files that need to be modified

File Purpose
cmd/system/<system>/<action>.go action constructor
cmd/system/<system>/spec.go Register a new action
cmd/system/<system>/<action>_test.go action tests
cmd/system/<system>/common.go Shared logic, optional

Constructor template

func newSomeActionCmd(deps systemcmd.BuildDeps) *cobra.Command {
    var (
        bizID   int
        stage   string
        body    string
        headers []string
    )

    cmd := &cobra.Command{
        Use:   "some_action",
        Short: "Short description",
        RunE: func(cmd *cobra.Command, args []string) error {
            runtime, err := systemcmd.ResolveRuntime(deps)
            if err != nil {
                return err
            }

            if err := systemcmd.ValidatePositiveIntFlag("bk_biz_id", bizID); err != nil {
                return err
            }

            bodyJSON, err := buildSomeBody(body, bizID)
            if err != nil {
                return err
            }

            return systemcmd.ExecuteRequest(cmd, runtime, "some_action", syslib.RequestSpec{
                GatewayName: gatewayName,
                Method:      "POST",
                Path:        "/api/v3/some/path/",
                BodyJSON:    bodyJSON,
                Headers:     headers,
                Stage:       stage,
                AuthConfig: &syslib.AuthConfig{
                    AppVerifiedRequired:        true,
                    UserVerifiedRequired:       true,
                    ResourcePermissionRequired: false,
                },
            }, nil)
        },
    }

    cmd.Flags().IntVar(&bizID, "bk_biz_id", 0, "Business ID")
    systemcmd.AddCommonRequestFlags(cmd, &stage, &body, &headers)
    return cmd
}

body composition mode

Prefer using the bodyOverride guard:

func buildSomeBody(bodyOverride string, bizID int) (string, error) {
    if bodyOverride != "" {
        return bodyOverride, nil
    }
    if err := systemcmd.ValidatePositiveIntFlag("bk_biz_id", bizID); err != nil {
        return "", err
    }
    return systemcmd.MarshalJSON(map[string]any{
        "bk_biz_id": bizID,
    })
}

mutate callback

When the envelope needs to be adjusted before stdout, pass in mutate:

return systemcmd.ExecuteRequest(cmd, runtime, "demo_action", spec,
    func(env *output.Envelope) error {
        if env.DryRun {
            env.Data = map[string]any{"received": localData}
            return nil
        }
        env.Data = map[string]any{
            "received": localData,
            "upstream": env.Data,
        }
        return nil
    })

No shared --body action

If the action manages body semantics itself:

  1. Use systemcmd.AddCommonRequestFlagsWithoutBody
  2. Declare local --body flag
  3. RequestSpec.BodyJSON determined by local logic

factory pattern

When multiple actions share the same set of flag/request structures, put the common parts into common.go, and use a spec struct + factory function to generate commands, avoiding copy-paste.

Multi-request orchestration

When pagination aggregation or multiple calls are needed:

  1. Use syslib.ExecuteRequest(runtime, spec) to send each request
  2. Use systemcmd.EnsureEnvelope(actionName, result.Envelope) to validate results
  3. Manually aggregate data
  4. Manually construct the final envelope

In spec.go mount in

builders := []func(systemcmd.BuildDeps) *cobra.Command{
    newExistingActionCmd,
    newSomeActionCmd,
}
for _, build := range builders {
    parent.AddCommand(build(deps))
}

Branch C: system already exists, add a new subsystem layer

Files that must be added or updated

File Purpose
cmd/system/<system>/spec.go In SystemSpec.Subsystems register subsystem
cmd/system/<system>/<subsystem>/spec.go NewSystemSpec() implementation, describing the subsystem's own YAML/Go actions
cmd/system/<system>/<subsystem>/actions.yaml Used when the subsystem needs YAML actions
cmd/system/<system>/<subsystem>/<action>.go Used when the subsystem needs Go-implemented actions
cmd/system/<system>/<subsystem>/common.go Multiple Go actions share logic, optional
cmd/system/<system>/<subsystem>/<subsystem>_suite_test.go Must exist when the subsystem has tests

subsystem spec.go template

package <subsystem>

import (
    "github.com/spf13/cobra"

    systemcmd "github.com/TencentBlueKing/bk-cli/internal/systemcmd"
)

func NewSystemSpec() systemcmd.SystemSpec {
    return systemcmd.SystemSpec{
        Name:        "<subsystem>",
        Description: "<subsystem> commands",
        YAMLFile:    "<system>/<subsystem>/actions.yaml",
        RegisterGoActions: func(parent *cobra.Command, deps systemcmd.BuildDeps) error {
            parent.AddCommand(newSomeActionCmd(deps))
            return nil
        },
    }
}

parent system registration template

func NewSystemSpec() systemcmd.SystemSpec {
    return systemcmd.SystemSpec{
        Name:        "<system>",
        Description: "<system> commands",
        YAMLFile:    "<system>/actions.yaml",
        RegisterGoActions: func(parent *cobra.Command, deps systemcmd.BuildDeps) error {
            parent.AddCommand(newParentActionCmd(deps))
            return nil
        },
        Subsystems: []systemcmd.SystemSpec{
            <subsystem>.NewSystemSpec(),
        },
    }
}

The parent system's YAMLFile and RegisterGoActions are both optional. Do not create meaningless parent actions just to mount a subsystem.

subsystem YAML top-level structure

name: <subsystem>
gateway_name: <subsystem-gateway-name>
description: "<subsystem> commands"
actions:
  - name: <action>

The subsystem YAML's gateway_name must be declared independently and not inherited from the parent system.

Testing and documentation requirements

At least add or update:

  • cmd/system/register_test.go: system registration behavior
  • cmd/system/<system>/<action>_test.go: Go-implemented action tests
  • cmd/system/<system>/<subsystem>/<action>_test.go: subsystem Go-implemented action tests
  • cmd/system/<system>/<system>_suite_test.go: Must exist when the system has tests
  • cmd/system/<system>/<subsystem>/<subsystem>_suite_test.go: Must exist when the subsystem has tests

If adding a public system, also update:

  • AGENTS.md
  • README.md
  • README_EN.md
  • tests/integration/AGENTS.md
  • skills/bk-cli-<system>/SKILL.md

If only changing existing commands or parameters, also update AGENTS.md、README.md、README_EN.md、tests/integration/AGENTS.md and related skills.

If the change affects the visible behavior of a public system, add or update:

  • tests/integration/cases/system/<system>/ YAML integration cases under
  • tests/integration/mock_api/app.py(only when httpbin is insufficient to express the behavior)

Final checklist

  1. First confirm whether the target system already exists, then choose the branch
  2. cmd/system/<system>.go and cmd/system/<system>/spec.go all exist
  3. SystemSpec.Name, YAML top-level name, and registration entry are consistent
  4. cmd/system/register.go of systemCatalog() already includes the target system
  5. The YAML file path is cmd/system/<system>/actions.yaml
  6. YAML actions all have complete authConfig
  7. Go-implemented action uses systemcmd.ResolveRuntime
  8. Go-implemented action uses systemcmd.ExecuteRequest or syslib.ExecuteRequest
  9. Shared flags, validation, and JSON parsing should preferably reuse systemcmd helper
  10. Tests should preferably reuse cmd/system/testutil
  11. Public system has added skills/bk-cli-<system>/SKILL.md
  12. The integration test cases related to the system have been tests/integration/cases/system/<system>/ completed or confirmed that no changes are needed
  13. If the integration test case author conventions have changed, updated tests/integration/AGENTS.md
  14. If adding a new subsystem, confirm that only one level of subsystem is used
  15. If adding a new subsystem, confirm that the subsystem action path is cmd/system/<system>/<subsystem>/...
  16. If adding a new subsystem, confirm that the subsystem YAML top-level name is <subsystem>, and independently declares gateway_name
  17. Confirm that the parent system's action name does not conflict with the subsystem name
  18. After completion, execute:
make fmt
make lint
make test
make build
make test-integration SCENARIO=<SCENARIO_ID>

Troubleshooting order

  1. First confirm whether the context and credentials are correct.
  2. Then confirm whether stage, tenant, timeout, header, and body conform to the mandatory checklist summary above; for details, refer to docs/design.md .
  3. Use --dry-run to inspect the final request construction.
  4. If problems still remain, go back to the specific system skill or docs/design.md to dig deeper.

Common errors

  • Not first determining whether the system already exists, resulting in duplicate creation of structures
  • Only wrote actions.yaml, but did not add cmd/system/<system>.go and spec.go
  • Forgot to add the system to systemCatalog()
  • The YAML file is not in cmd/system/<system>/actions.yaml
  • YAML action is missing authConfig
  • In YAML params wrote in: body
  • Go-implemented action bypasses ResolveRuntime or ExecuteRequest
  • Duplicate definition of test helper in the system directory
  • Added a public system but did not add skills/bk-cli-<system>/SKILL.md
  • The import alias is not syslib / systemcmd
  • The API list provided by the user was already divided into modules such as pipeline/codecc/stream, but the agent did not first let the user choose between flat actions and subsystems
  • The subsystem YAML was written as the parent system's name
  • The subsystem YAML attempts to omit gateway_name and inherit the parent system
  • Created multiple levels of subsystems
  • The parent system action and the subsystem use the same command name