Files
WeKnora/cli/cmd/agent/delete.go
T
nullkey c87e35b34b chore(cli): polish + docs sync + pre-PR audit fixes
Code-reuse polish (post-implementation review pass):
- Extract text.OneLine(maxWidth, s) helper combining preview-row
  normalization (newline/CR/tab → space) with text.Truncate's
  UTF-8-safe truncation. Replaces agent/view.go truncate1Line (ASCII
  '...' + byte-slice CJK-unsafe) and chunk/list.go singleLine.
- Lift cmdutil.OpenInput(path) for the '-' = stdin / else os.Open
  pattern shared across agent create/edit and the api command.
  Replaces agent/create.go's private openInput.
- Strip inline doc-spec parentheticals from source comments — those
  belong in commit messages and project docs, not in source where
  they rot.

Pre-PR audit fixes:
- doc upload: reject `--metadata` paired with `--from-url` as
  input.invalid_argument up-front (the URL-ingest request type has
  no metadata field server-side, so the pair would otherwise silently
  drop). Long help and CHANGELOG updated to call out the asymmetry.
- doc upload (file path): map sdk.ErrDuplicateFile sentinel to
  resource.already_exists. The sentinel arrives with no "HTTP error <n>:"
  prefix because the SDK short-circuits on file-hash before reading the
  HTTP status, so the previous WrapHTTP fall-through misclassified it
  as network.error with a misleading "check base URL reachability" hint.
  The --from-url branch already handled ErrDuplicateURL this way; this
  closes the asymmetry. Caught by e2e re-upload of an already-ingested
  file; regression test added.
- README exit-10 enumeration adds `agent delete` and `chunk delete`
  (these were missing alongside the v0.5 destructive verbs they were
  meant to gate).

Docs sync:
- cli/README.md: command tree now includes the chunk subtree; adds
  agent / chunk lines to the 5-minute quickstart; adds a "Contributing
  / Reporting issues" section pointing at the repo's SECURITY.md and
  AGENTS.md; drops third-party CLI parallels from the surface
  description.
- cli/AGENTS.md: "Command surface design SOP" gains the
  flag-vs-escape-hatch step. "CRUD command flag canon" renamed to the
  hard-required-flags pattern with the contrast (TTY-prompts-fill)
  defined inline rather than via opaque shorthand.
- cli/CHANGELOG.md: search docs case-sensitivity shift promoted to its
  own #### Breaking changes subsection. MCP doc_list filter count
  corrected from 5 to 6. Drops the bogus go.mod yaml.v3 entry (yaml.v3
  was already a dependency on main; v0.5 added zero go.mod lines).
  Replaces internal-Go identifiers (fuzzyTime, NoOptDefVal) with
  user-language and drops the § section-symbol jargon.
2026-05-16 16:56:33 +08:00

97 lines
3.3 KiB
Go

package agentcmd
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/Tencent/WeKnora/cli/internal/cmdutil"
"github.com/Tencent/WeKnora/cli/internal/iostreams"
"github.com/Tencent/WeKnora/cli/internal/prompt"
)
// agentDeleteFields enumerates the JSON discovery fields for `agent delete`.
// Result payload is a tiny {id, deleted} object — mirrors `kb delete`.
var agentDeleteFields = []string{"id", "deleted"}
type DeleteOptions struct {
AgentID string
Yes bool // sourced from the global -y/--yes persistent flag
}
// DeleteService is the narrow SDK surface this command depends on.
type DeleteService interface {
DeleteAgent(ctx context.Context, id string) error
}
// deleteResult is the typed payload emitted on success in JSON mode.
type deleteResult struct {
ID string `json:"id"`
Deleted bool `json:"deleted"`
}
// Delete is NOT idempotent on a missing id — it surfaces resource.not_found
// rather than silently exiting 0. Idempotent-already-true semantics are
// reserved for unlink-style local cleanups, not server-side resource removal.
const agentDeleteLong = `Permanently delete a custom agent.
Prompts for confirmation by default when stdout is a TTY and --json is
not set. Pass -y/--yes (the global flag) to skip the prompt (required in
agent / CI / piped contexts).
Typed exit codes:
resource.not_found no agent with the given id (exit 4)
auth.forbidden caller lacks delete permission on the agent (exit 3)
input.confirmation_required destructive op without -y on a TTY (exit 10)
AI agents: This is a high-risk write. Without -y/--yes the CLI exits 10
and writes input.confirmation_required to stderr. NEVER auto-pass -y
without the user's explicit go-ahead — the exit-10 protocol exists
exactly to guard against unintended deletes.`
const agentDeleteExample = ` weknora agent delete ag_abc # interactive confirm
weknora agent delete ag_abc -y # no prompt
weknora agent delete ag_abc -y --json # bare {id, deleted:true} JSON`
// NewCmdDelete builds `weknora agent delete <agent-id>`.
func NewCmdDelete(f *cmdutil.Factory) *cobra.Command {
opts := &DeleteOptions{}
cmd := &cobra.Command{
Use: "delete <agent-id>",
Short: "Delete a custom agent",
Long: agentDeleteLong,
Example: agentDeleteExample,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
jopts, err := cmdutil.CheckJSONFlags(cmd)
if err != nil {
return err
}
opts.AgentID = args[0]
opts.Yes, _ = cmd.Flags().GetBool("yes")
cli, err := f.Client()
if err != nil {
return err
}
return runDelete(cmd.Context(), opts, jopts, cli, f.Prompter())
},
}
cmdutil.AddJSONFlags(cmd, agentDeleteFields)
return cmd
}
func runDelete(ctx context.Context, opts *DeleteOptions, jopts *cmdutil.JSONOptions, svc DeleteService, p prompt.Prompter) error {
if err := cmdutil.ConfirmDestructive(p, opts.Yes, jopts.Enabled(), "agent", opts.AgentID); err != nil {
return err
}
if err := svc.DeleteAgent(ctx, opts.AgentID); err != nil {
return cmdutil.WrapHTTP(err, "delete agent %s", opts.AgentID)
}
if jopts.Enabled() {
return jopts.Emit(iostreams.IO.Out, deleteResult{ID: opts.AgentID, Deleted: true})
}
fmt.Fprintf(iostreams.IO.Out, "✓ Deleted agent %s\n", opts.AgentID)
return nil
}