WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
package cmdutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/Tencent/WeKnora/cli/internal/iostreams"
|
||||
"github.com/Tencent/WeKnora/cli/internal/prompt"
|
||||
)
|
||||
|
||||
// BuildRetryArgv assembles the directly-executable retry argv for a
|
||||
// confirmation-gated command: head (e.g. []string{"weknora","kb","update",id})
|
||||
// followed by each *changed* flag named in allow (as "--name", "value") in
|
||||
// pflag's visit order, plus a trailing "-y".
|
||||
//
|
||||
// Slice-typed flags (StringSlice / StringArray) expand to repeated
|
||||
// "--name", "value" pairs so the argv is re-executable by shells and agents.
|
||||
// Scalar flags use f.Value.String(). Flags not in allow are skipped (callers
|
||||
// still exclude secrets / stdin-only values such as --api-key-stdin).
|
||||
// Replaces the per-command build*RetryCmd helpers.
|
||||
func BuildRetryArgv(c *cobra.Command, head []string, allow ...string) []string {
|
||||
allowed := make(map[string]struct{}, len(allow))
|
||||
for _, a := range allow {
|
||||
allowed[a] = struct{}{}
|
||||
}
|
||||
parts := append([]string{}, head...)
|
||||
c.Flags().Visit(func(f *pflag.Flag) {
|
||||
if _, ok := allowed[f.Name]; !ok {
|
||||
return
|
||||
}
|
||||
// Expand multi-value flags as repeated --flag value pairs so retry_argv
|
||||
// is directly executable (pflag's String() returns "[a,b]", which is not).
|
||||
if sv, ok := f.Value.(pflag.SliceValue); ok {
|
||||
for _, v := range sv.GetSlice() {
|
||||
parts = append(parts, "--"+f.Name, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
parts = append(parts, "--"+f.Name, f.Value.String())
|
||||
})
|
||||
return append(parts, "-y")
|
||||
}
|
||||
|
||||
// confirmCaveat returns the trailing safety note shown after the interactive
|
||||
// prompt, tailored to the verb. Deletes/removals are irreversible; edits
|
||||
// overwrite prior values but are recoverable if you know the old value.
|
||||
func confirmCaveat(verb string) string {
|
||||
switch verb {
|
||||
case "edit":
|
||||
return "This overwrites the current values."
|
||||
default: // delete / remove — irreversible
|
||||
return "This cannot be undone."
|
||||
}
|
||||
}
|
||||
|
||||
// titleFirst upper-cases the first rune so the interactive prompt reads as a
|
||||
// sentence ("Delete …?" / "Edit …?") without pulling in golang.org/x/text.
|
||||
func titleFirst(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
r, size := utf8.DecodeRuneInString(s)
|
||||
return string(unicode.ToUpper(r)) + s[size:]
|
||||
}
|
||||
|
||||
// ConfirmDestructiveBatch is the multi-id flavor of ConfirmDestructive: same
|
||||
// behavior matrix (yes / non-TTY / TTY-prompt / user-no) but the prompt text
|
||||
// reflects the count, not a single id. Used by `doc delete <id> [<id>...]`
|
||||
// — one -y confirms all items in the batch.
|
||||
//
|
||||
// Pass n = total count of items about to be deleted.
|
||||
// action is the namespaced action verb (e.g. "doc.delete") for the risk envelope.
|
||||
// retryArgv is the directly-executable retry argv array
|
||||
// (e.g. []string{"weknora","doc","delete","a","b","-y"}); pass nil when no
|
||||
// clean retry argv is available.
|
||||
func ConfirmDestructiveBatch(p prompt.Prompter, yes, jsonOut bool, verb, what string, n int, action string, retryArgv []string) error {
|
||||
if yes {
|
||||
return nil
|
||||
}
|
||||
if !iostreams.IO.IsStdoutTTY() || jsonOut {
|
||||
return NewError(
|
||||
CodeInputConfirmationRequired,
|
||||
fmt.Sprintf("%s %d %s(s) requires explicit confirmation: re-run with -y/--yes", verb, n, what),
|
||||
).
|
||||
WithRetryArgv(retryArgv).
|
||||
WithRisk("destructive", action)
|
||||
}
|
||||
ok, err := p.Confirm(fmt.Sprintf("%s %d %s(s)? %s", titleFirst(verb), n, what, confirmCaveat(verb)), false)
|
||||
if err != nil {
|
||||
return Wrapf(CodeInputMissingFlag, err, "confirm batch delete")
|
||||
}
|
||||
if !ok {
|
||||
fmt.Fprintln(iostreams.IO.Err, "Aborted.")
|
||||
return NewError(CodeUserAborted, "delete aborted")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfirmDestructive guards a destructive operation (delete, force-overwrite)
|
||||
// behind explicit user approval. Behavior matrix:
|
||||
//
|
||||
// yes=true → proceed (explicit user opt-in via -y/--yes)
|
||||
// non-TTY OR jsonOut → return CodeInputConfirmationRequired (exit 10);
|
||||
// no UI to prompt, agent/CI must re-invoke with -y
|
||||
// after the human explicitly approves
|
||||
// TTY + interactive → prompt; user-yes proceeds, user-no returns
|
||||
// CodeUserAborted ("Aborted." to stderr)
|
||||
// prompter error → returns CodeInputMissingFlag (rare; stdin closed
|
||||
// mid-prompt)
|
||||
//
|
||||
// The non-TTY branch is the destructive-write protocol: high-risk writes
|
||||
// always require explicit confirmation in scripted contexts, never silent
|
||||
// proceed. See cli/README.md "Exit codes".
|
||||
//
|
||||
// `yes` should be sourced from the persistent global -y/--yes flag.
|
||||
// action is the namespaced action verb (e.g. "kb.delete") for the risk envelope.
|
||||
// retryArgv is the directly-executable retry argv array
|
||||
// (e.g. []string{"weknora","kb","delete","kb_x","-y"}); pass nil when no
|
||||
// clean retry argv is available.
|
||||
func ConfirmDestructive(p prompt.Prompter, yes, jsonOut bool, verb, what, id, action string, retryArgv []string) error {
|
||||
return confirmGated(p, yes, jsonOut, verb, what, id, action, RiskDestructive, retryArgv)
|
||||
}
|
||||
|
||||
// ConfirmWrite guards a reversible metadata write (kb / agent / doc update).
|
||||
// Same confirmation gate as ConfirmDestructive — a labeling-accuracy variant,
|
||||
// NOT a weaker gate — but tags the risk envelope at the "write" level so an
|
||||
// agent can distinguish a recoverable edit from an irreversible delete.
|
||||
func ConfirmWrite(p prompt.Prompter, yes, jsonOut bool, verb, what, id, action string, retryArgv []string) error {
|
||||
return confirmGated(p, yes, jsonOut, verb, what, id, action, RiskWrite, retryArgv)
|
||||
}
|
||||
|
||||
// confirmGated is the shared single-resource confirmation gate behind
|
||||
// ConfirmDestructive / ConfirmWrite. `level` is the risk.level reported on the
|
||||
// exit-10 envelope (RiskDestructive for irreversible ops, RiskWrite for
|
||||
// recoverable edits). Behavior matrix:
|
||||
//
|
||||
// yes=true → proceed (explicit user opt-in via -y/--yes)
|
||||
// non-TTY OR jsonOut → CodeInputConfirmationRequired (exit 10) + risk{level,action};
|
||||
// no UI to prompt, agent/CI must re-invoke with -y after
|
||||
// the human explicitly approves
|
||||
// TTY + interactive → prompt; user-yes proceeds, user-no returns CodeUserAborted
|
||||
// prompter error → CodeInputMissingFlag (rare; stdin closed mid-prompt)
|
||||
func confirmGated(p prompt.Prompter, yes, jsonOut bool, verb, what, id, action, level string, retryArgv []string) error {
|
||||
if yes {
|
||||
return nil
|
||||
}
|
||||
if !iostreams.IO.IsStdoutTTY() || jsonOut {
|
||||
return NewError(
|
||||
CodeInputConfirmationRequired,
|
||||
fmt.Sprintf("%s %s %s requires explicit confirmation: re-run with -y/--yes", verb, what, id),
|
||||
).
|
||||
WithRetryArgv(retryArgv).
|
||||
WithRisk(level, action)
|
||||
}
|
||||
ok, err := p.Confirm(fmt.Sprintf("%s %s %s? %s", titleFirst(verb), what, id, confirmCaveat(verb)), false)
|
||||
if err != nil {
|
||||
return Wrapf(CodeInputMissingFlag, err, "confirm %s", verb)
|
||||
}
|
||||
if !ok {
|
||||
fmt.Fprintln(iostreams.IO.Err, "Aborted.")
|
||||
return NewError(CodeUserAborted, fmt.Sprintf("%s aborted", verb))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user