mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-08-30 16:53:21 +08:00
cc8254f862
Two intertwined mainstream-alignment moves bundled because they share
the migration target (every command's --json path):
1. Drop --dry-run entirely. Survey of comparable API-wrapper CLIs
(gh, aws, stripe, lark): none expose --dry-run. The mainstream that
does (kubectl/git/helm/ansible) operates on declarative manifests
or local state where the preview is materially different from the
executed action. WeKnora's CLI just echoed the same parameters
that would have gone on the wire — the preview added no real
signal over `--help` + reading the call site. Removes:
- root --dry-run persistent flag + cmdutil/dryrun.go
- DryRun fields + EmitDryRun calls in 12 write commands
- format.Envelope.DryRun field
- 8 corresponding *_test.go cases
- --dry-run mention from README.md and CHANGELOG.md
- "dry_run":false from 16 golden envelopes
2. Migrate every --json output to bare data:
- New format.WriteJSON / WriteJSONFiltered helpers
(cli/internal/format/bare.go) share filterArrayItems /
filterObjectKeys / writeJQ with the (still-live for now) envelope
filter helpers.
- Read commands (kb/doc/session list+view, search chunks/docs/
sessions/kb, auth list/status, agent list/view, context list,
doctor) emit bare arrays / objects on stdout.
- Write commands (kb create/edit/delete/pin/empty, doc upload/
upload_recursive/delete, session delete, auth login/logout/
refresh/token, link/unlink, context add/use/remove, agent
invoke, chat, api, version) emit bare result objects. Risk
classification dropped — the resource + exit code already
convey the action.
Per-command shape changes:
list / search → []T (was {ok, data:{items:[…]}})
view → T (was {ok, data:T, _meta:…})
create / edit → T
delete / pin / etc. → {id, …action result…}
doctor → {summary, checks}
api → {status, headers, body}
_meta dropped on the read path:
pagination (page/page_size/total/has_more) — agents iterate with
--all-pages or accept --limit (gh CLI parity);
kb_id / context echo — caller already knows what it asked for.
Acceptance contract goldens regenerated for the new bare shape.
Error envelope on stdout (PrintErrorEnvelope) stays live for now —
the envelope-infra deletion lands in the next commit.
129 lines
4.8 KiB
Go
129 lines
4.8 KiB
Go
// Package format renders command output: JSON envelope, jq, template, table.
|
|
//
|
|
// Non-TTY default = JSON envelope (ADR-5). Envelope schema is the contract
|
|
// shared with `weknora mcp serve` tools and external agents.
|
|
package format
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
)
|
|
|
|
// Envelope is the canonical success/failure shape returned by every command.
|
|
//
|
|
// v0.2 ADR-3 added Notice / Risk. Both are absent on read-only commands;
|
|
// write commands populate Risk even on success so agents can record what
|
|
// action ran.
|
|
type Envelope struct {
|
|
OK bool `json:"ok"`
|
|
Data any `json:"data,omitempty"`
|
|
Error *ErrorBody `json:"error,omitempty"`
|
|
Meta *Meta `json:"_meta,omitempty"`
|
|
Notice *Notice `json:"_notice,omitempty"`
|
|
Risk *Risk `json:"risk,omitempty"`
|
|
}
|
|
|
|
// Notice carries system-level advisories independent of the command outcome:
|
|
// CLI update available, server-CLI version skew, etc. Agents read these to
|
|
// surface upgrade prompts to users without polluting `data`.
|
|
type Notice struct {
|
|
Update *UpdateNotice `json:"update,omitempty"`
|
|
VersionSkew *VersionSkewNotice `json:"version_skew,omitempty"`
|
|
}
|
|
|
|
// UpdateNotice indicates a newer CLI version is available.
|
|
type UpdateNotice struct {
|
|
Available bool `json:"available"`
|
|
Current string `json:"current"`
|
|
Latest string `json:"latest,omitempty"`
|
|
}
|
|
|
|
// VersionSkewNotice indicates the server is behind the CLI within the compat
|
|
// window. `Level` mirrors doctor's status semantics: "warn" (degraded but
|
|
// functional) or "error" (out of compat).
|
|
type VersionSkewNotice struct {
|
|
Client string `json:"client"`
|
|
Server string `json:"server"`
|
|
Level string `json:"level"`
|
|
}
|
|
|
|
// Risk classifies the operation the user is performing — not the error.
|
|
// Agents inspect this on every envelope. When Level == RiskHighRiskWrite and
|
|
// the operation requires confirmation (no -y), the CLI exits 10. See
|
|
// cli/AGENTS.md "Exit codes".
|
|
type Risk struct {
|
|
Level RiskLevel `json:"level"`
|
|
Action string `json:"action,omitempty"`
|
|
}
|
|
|
|
// Meta carries non-payload context fields useful to agents and observability.
|
|
//
|
|
// Pagination metadata (Page / PageSize / Total) lives here rather than in
|
|
// data.{...} so every list command's `data` field has the same shape —
|
|
// always `{items: [...]}` — and agents can branch on the resource type
|
|
// without per-list parser variants.
|
|
type Meta struct {
|
|
Context string `json:"context,omitempty"`
|
|
TenantID uint64 `json:"tenant_id,omitempty"`
|
|
KBID string `json:"kb_id,omitempty"`
|
|
RequestID string `json:"request_id,omitempty"`
|
|
NextCursor string `json:"next_cursor,omitempty"`
|
|
HasMore bool `json:"has_more,omitempty"`
|
|
Page int `json:"page,omitempty"`
|
|
PageSize int `json:"page_size,omitempty"`
|
|
Total int64 `json:"total,omitempty"`
|
|
Warnings []string `json:"warnings,omitempty"`
|
|
AppliedFilters []string `json:"applied_filters,omitempty"`
|
|
}
|
|
|
|
// RiskLevel classifies an operation. Agents use this to decide whether to
|
|
// retry, require explicit user approval, or stop.
|
|
type RiskLevel string
|
|
|
|
const (
|
|
RiskRead RiskLevel = "read"
|
|
RiskWrite RiskLevel = "write"
|
|
RiskHighRiskWrite RiskLevel = "high-risk-write"
|
|
)
|
|
|
|
// ErrorBody is the failure shape. `code` is a stable namespaced ID (e.g.
|
|
// "auth.unauthenticated"); `hint` is an actionable next step. Operation-level
|
|
// risk lives at the envelope level (Envelope.Risk), not here.
|
|
type ErrorBody struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Hint string `json:"hint,omitempty"`
|
|
RequestID string `json:"request_id,omitempty"`
|
|
Context string `json:"context,omitempty"`
|
|
Retryable bool `json:"retryable,omitempty"`
|
|
ConsoleURL string `json:"console_url,omitempty"`
|
|
Details map[string]any `json:"details,omitempty"`
|
|
}
|
|
|
|
// WriteEnvelope serializes env as one-line JSON to w. Used for non-TTY output
|
|
// and for `--json` per-command mode (the omnibus `--agent` mode-switch was
|
|
// removed in v0.2 ADR-3; see cli/AGENTS.md for the agent contract).
|
|
func WriteEnvelope(w io.Writer, env Envelope) error {
|
|
enc := json.NewEncoder(w)
|
|
enc.SetEscapeHTML(false)
|
|
return enc.Encode(env)
|
|
}
|
|
|
|
// Success constructs a success envelope.
|
|
func Success(data any, meta *Meta) Envelope {
|
|
return Envelope{OK: true, Data: data, Meta: meta}
|
|
}
|
|
|
|
// SuccessWithRisk is Success + a per-operation Risk classification. Used by
|
|
// every write command (kb create/delete, doc upload/delete, api POST/PUT/
|
|
// DELETE, ...) so an agent reading any envelope can tell what kind of
|
|
// operation produced it without parsing the data shape.
|
|
func SuccessWithRisk(data any, meta *Meta, risk *Risk) Envelope {
|
|
return Envelope{OK: true, Data: data, Meta: meta, Risk: risk}
|
|
}
|
|
|
|
// Failure constructs a failure envelope.
|
|
func Failure(err *ErrorBody) Envelope {
|
|
return Envelope{OK: false, Error: err}
|
|
}
|