cli

package
v0.2.3-dev Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package cli provides the IPC bridge to `aikey _internal *` and the shared HTTP error model used by every handler that talks to the cli.

Why a separate package: vault, vault-CRUD, and import all spawn the cli with the same envelope shape and report errors with the same I_* code surface. Centralising them here keeps the contract single-sourced; each consumer imports cli instead of redeclaring envelopes / error codes.

Index

Constants

View Source
const (
	// Protocol + spawn layer
	ErrCliNotFound       = "I_CLI_NOT_FOUND"       // aikey binary missing in PATH and ~/.aikey/bin
	ErrCliSpawnFailed    = "I_CLI_SPAWN_FAILED"    // os/exec returned an error before cli ran
	ErrCliTimeout        = "I_CLI_TIMEOUT"         // ctx deadline reached while waiting for cli stdout
	ErrCliMalformedReply = "I_CLI_MALFORMED_REPLY" // stdout JSON unparseable or missing required fields

	// Vault session layer
	ErrVaultLocked       = "I_VAULT_LOCKED"        // request targets an unlock-required route while session is absent
	ErrVaultUnlockFailed = "I_VAULT_UNLOCK_FAILED" // password did not produce a verifying key (wraps cli I_VAULT_KEY_INVALID)
	ErrVaultNoSession    = "I_VAULT_NO_SESSION"    // session id cookie missing or expired

	// Request-level
	ErrBadRequest = "I_BAD_REQUEST" // malformed JSON body or missing required field

	// User Vault CRUD layer (Web page /user/vault — 2026-04-23 decision set)
	ErrOAuthAddViaCLI       = "I_OAUTH_ADD_VIA_CLI"      // POST /vault/entry called with target=oauth (OAuth add flow lives in CLI)
	ErrUnknownTarget        = "I_UNKNOWN_TARGET"         // target is not personal|oauth|team, or team is not yet implemented
	ErrAliasSuffixExhausted = "I_ALIAS_SUFFIX_EXHAUSTED" // auto -2/-3 retry ran 20× and still conflicted (extreme edge case)
	ErrUnlockRateLimited    = "I_UNLOCK_RATE_LIMITED"    // too many unlock attempts from one source; online brute-force defense
)

I_* error codes surfaced to the Web UI. Mirror the set emitted by the Rust cli where applicable (aikey-cli/src/error_codes.rs); additional codes here cover Go-side orchestration failures (cli not found / spawn timeout / session missing) that have no cli analogue.

View Source
const PlaceholderHex = "0000000000000000000000000000000000000000000000000000000000000000"

PlaceholderHex is the 64-char all-zero vault_key_hex used for cli actions that perform only format validation (parse, metadata). See aikey-cli/src/commands_internal/parse.rs docblock: "only checks format, does not verify against vault".

Variables

This section is empty.

Functions

func WriteCliError

func WriteCliError(w http.ResponseWriter, result *Result)

WriteCliError writes a 4xx/5xx response mirroring the cli Result error branch. It maps a small set of known cli codes to HTTP status and falls back to 500 for the rest.

func WriteEnvelope

func WriteEnvelope(w http.ResponseWriter, r *Result)

WriteEnvelope relays a cli Result verbatim to the HTTP client. ok branch → 200 + {status, data}; error branch → status-mapped + {status, error_code, error_message}. Preserves request_id if the cli echoed one.

func WriteErr

func WriteErr(w http.ResponseWriter, code, msg string)

WriteErr writes a JSON error response with a status chosen from the code.

Status-code mapping (kept deliberately narrow so the Web UI and CLI can both reason about it):

  • 401 Unauthorized — missing session cookie; top-level auth problem.
  • 422 Unprocessable — vault-specific business errors (wrong master password, cli says I_VAULT_KEY_INVALID). NOT 401 because the global httpClient interceptor treats 401 as "JWT expired" and redirects to /login, which would be wrong UX for a vault password typo. Self-review 2026-04-22 caught this.
  • 400 Bad Request — malformed JSON / schema violation.
  • 503 Service Unavail. — cli binary missing (production container).
  • 504 Gateway Timeout — cli spawn timed out.
  • 500 Internal — everything else.

func WriteInvokeError

func WriteInvokeError(w http.ResponseWriter, err error)

WriteInvokeError maps a Bridge.Invoke error to an HTTP response with the correct status code. Uses the InvokeError.Code when available and falls back to ErrCliSpawnFailed (500) for anything else.

Types

type Bridge

type Bridge struct {
	// BinaryPath is the resolved aikey executable. Empty => look up lazily.
	BinaryPath string
	// Timeout applied to each invocation unless the caller's ctx deadline is sooner.
	Timeout time.Duration
	Logger  *slog.Logger
}

Bridge spawns `aikey _internal <subcommand>` with a stdin envelope and parses the single-line stdout Result. One spawn per call — the cli is stateless and re-exec cost is acceptable (measured ~30ms on macOS, per Stage 0.3 subprocess latency baseline).

func New

func New(logger *slog.Logger) *Bridge

New builds a bridge with a default 15s timeout. The binary is resolved lazily on first call so local-server can boot even if the cli isn't installed yet (the page still renders; only the action endpoints fail).

func (*Bridge) Invoke

func (b *Bridge) Invoke(
	ctx context.Context,
	subcommand string,
	action string,
	vaultKeyHex string,
	requestID string,
	payload any,
) (*Result, error)

Invoke spawns one `aikey _internal <subcommand>` and returns the parsed Result. A non-nil error is returned when the envelope's Status is not "ok" wrt spawn / parse failure (the caller is expected to surface ErrorCode / ErrorMessage to the browser via WriteCliError). Note: a well-formed cli error reply (status="error" with codes) returns a Result with no error here — caller decides what to do.

subcommand is the top-level `_internal` subcommand name ("parse", "vault-op", "query", "update-alias"). action is the sub-action inside vault-op (e.g. "verify", "metadata", "batch_import"); for subcommands that don't use the action field (parse), pass "".

func (*Bridge) InvokeInit

func (b *Bridge) InvokeInit(
	ctx context.Context,
	password string,
	requestID string,
) (*Result, error)

InvokeInit spawns `aikey _internal init --stdin-json` with `{password, request_id}` and returns the parsed Result. Used by the web-driven first-run flow (POST /api/user/vault/init) per 20260430-个人vault-Web首次设置-方案A.md.

Why a separate method rather than reusing Invoke: init.rs reads its own envelope shape (no vault_key_hex; vault doesn't exist yet) so the standard envelope wrapper would just add fields the cli ignores.

type InvokeError

type InvokeError struct {
	Code string
	Msg  string
}

InvokeError carries both an I_* code and a human message so handlers can map to the correct HTTP status. Wrapping via fmt.Errorf with %s prefix worked for display but hid the code from WriteErr's status table, which made every spawn-level failure surface as 500 regardless of its actual cause (the bug this type fixes — 2026-04-22 self-review).

func (*InvokeError) Error

func (e *InvokeError) Error() string

type JSONError

type JSONError struct {
	Status       string `json:"status"` // always "error"
	ErrorCode    string `json:"error_code"`
	ErrorMessage string `json:"error_message"`
}

JSONError is the HTTP-side error envelope. Shape intentionally matches the cli's ResultEnvelope error branch ({status, error_code, error_message}) so the Web UI has one parser for both layers.

type Result

type Result struct {
	RequestID    string          `json:"request_id,omitempty"`
	Status       string          `json:"status"`
	Data         json.RawMessage `json:"data,omitempty"`
	ErrorCode    string          `json:"error_code,omitempty"`
	ErrorMessage string          `json:"error_message,omitempty"`
}

Result mirrors ResultEnvelope on the cli side. Exported so handlers in vault / intake packages can pattern-match on Status / ErrorCode.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL