fix(cli): align help with README
This commit is contained in:
parent
b5776e4e1f
commit
594ecadfdd
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"id": "04-17-align-readme-cli-help",
|
||||||
|
"name": "04-17-align-readme-cli-help",
|
||||||
|
"title": "Align README and CLI help",
|
||||||
|
"description": "",
|
||||||
|
"status": "completed",
|
||||||
|
"dev_type": null,
|
||||||
|
"scope": null,
|
||||||
|
"package": null,
|
||||||
|
"priority": "P2",
|
||||||
|
"creator": "Apale",
|
||||||
|
"assignee": "Apale",
|
||||||
|
"createdAt": "2026-04-17",
|
||||||
|
"completedAt": "2026-04-17",
|
||||||
|
"branch": null,
|
||||||
|
"base_branch": "master",
|
||||||
|
"worktree_path": null,
|
||||||
|
"current_phase": 0,
|
||||||
|
"next_action": [
|
||||||
|
{
|
||||||
|
"phase": 1,
|
||||||
|
"action": "brainstorm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": 2,
|
||||||
|
"action": "research"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": 3,
|
||||||
|
"action": "implement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": 4,
|
||||||
|
"action": "check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": 5,
|
||||||
|
"action": "update-spec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"phase": 6,
|
||||||
|
"action": "record-session"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"commit": null,
|
||||||
|
"pr_url": null,
|
||||||
|
"subtasks": [],
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"relatedFiles": [],
|
||||||
|
"notes": "",
|
||||||
|
"meta": {}
|
||||||
|
}
|
||||||
@ -357,6 +357,8 @@ olpx opencode sync --help
|
|||||||
olpx --config /path/to/config.json doctor
|
olpx --config /path/to/config.json doctor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
命令级行为、默认值、写入范围与副作用,以对应命令的 `--help` 为准;README 主要保留快速上手与背景说明。
|
||||||
|
|
||||||
一个最小配置示例:
|
一个最小配置示例:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@ -91,6 +91,10 @@ interactions when the same provider is shared across multiple aliases.
|
|||||||
|
|
||||||
## CLI reference
|
## CLI reference
|
||||||
|
|
||||||
|
For exact command behavior, defaults, write scope, and side effects, prefer the
|
||||||
|
matching `--help` page. This README is the quick-start narrative, while CLI help
|
||||||
|
is the authoritative local execution contract.
|
||||||
|
|
||||||
- `olpx serve` — run the proxy
|
- `olpx serve` — run the proxy
|
||||||
- `olpx doctor` — validate config
|
- `olpx doctor` — validate config
|
||||||
- `olpx provider {add,list,enable,disable,remove,import-opencode}`
|
- `olpx provider {add,list,enable,disable,remove,import-opencode}`
|
||||||
|
|||||||
@ -14,6 +14,19 @@ func newAliasCmd() *cobra.Command {
|
|||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "alias",
|
Use: "alias",
|
||||||
Short: "Manage logical aliases routed by olpx",
|
Short: "Manage logical aliases routed by olpx",
|
||||||
|
Long: `Alias commands manage the user-facing model names that OpenCode sees as
|
||||||
|
olpx/<alias>.
|
||||||
|
|
||||||
|
Each alias contains an ordered target chain of provider/model pairs. Target
|
||||||
|
order is operational: olpx tries targets in order and only fails over before any
|
||||||
|
response bytes are sent downstream.
|
||||||
|
|
||||||
|
Common workflow: create an alias, bind primary and fallback targets, inspect the
|
||||||
|
result with alias list, then run doctor and opencode sync.`,
|
||||||
|
Example: ` olpx alias add --name gpt-5.4 --display-name "GPT 5.4"
|
||||||
|
olpx alias bind --alias gpt-5.4 --provider su8 --model gpt-5.4
|
||||||
|
olpx alias bind --alias gpt-5.4 --provider codex --model GPT-5.4
|
||||||
|
olpx alias list`,
|
||||||
}
|
}
|
||||||
c.AddCommand(newAliasAddCmd(), newAliasListCmd(), newAliasBindCmd(), newAliasUnbindCmd(), newAliasRemoveCmd())
|
c.AddCommand(newAliasAddCmd(), newAliasListCmd(), newAliasBindCmd(), newAliasUnbindCmd(), newAliasRemoveCmd())
|
||||||
return c
|
return c
|
||||||
@ -25,6 +38,18 @@ func newAliasAddCmd() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "add",
|
Use: "add",
|
||||||
Short: "Create or update an alias (without targets)",
|
Short: "Create or update an alias (without targets)",
|
||||||
|
Long: `alias add creates or updates alias metadata in local olpx config.
|
||||||
|
|
||||||
|
It writes the alias record itself, but it does not add or validate targets.
|
||||||
|
Enabled aliases still need at least one routable target before doctor and
|
||||||
|
opencode sync will treat them as usable.
|
||||||
|
|
||||||
|
When updating an existing alias, omitted display-name preserves the current
|
||||||
|
value and existing targets stay attached. Typical next step: add targets with
|
||||||
|
olpx alias bind.`,
|
||||||
|
Example: ` olpx alias add --name gpt-5.4 --display-name "GPT 5.4"
|
||||||
|
olpx alias add --name gpt-5.4-mini --disabled
|
||||||
|
olpx alias add --name gpt-5.4 --display-name "GPT 5.4 Reasoning"`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return fmt.Errorf("--name is required")
|
return fmt.Errorf("--name is required")
|
||||||
@ -63,6 +88,16 @@ func newAliasListCmd() *cobra.Command {
|
|||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List aliases and their target chains",
|
Short: "List aliases and their target chains",
|
||||||
|
Long: `alias list prints aliases from local olpx config together with their target
|
||||||
|
chains.
|
||||||
|
|
||||||
|
Output shows alias enabled state, target order, target enabled markers, and a
|
||||||
|
note when a referenced provider is missing or disabled. This is the easiest way
|
||||||
|
to verify failover order before running doctor or opencode sync.
|
||||||
|
|
||||||
|
This command does not modify config and does not contact upstream providers.`,
|
||||||
|
Example: ` olpx alias list
|
||||||
|
olpx --config /path/to/config.json alias list`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cfg, err := loadCfg()
|
cfg, err := loadCfg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,6 +142,18 @@ func newAliasBindCmd() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "bind",
|
Use: "bind",
|
||||||
Short: "Append a target (provider/model) to an alias in failover order",
|
Short: "Append a target (provider/model) to an alias in failover order",
|
||||||
|
Long: `alias bind appends one provider/model target to an alias's ordered failover
|
||||||
|
chain in local olpx config.
|
||||||
|
|
||||||
|
The provider must already exist. If the alias does not exist yet, this command
|
||||||
|
auto-creates an enabled alias for convenience. Binding does not test upstream
|
||||||
|
health or credentials.
|
||||||
|
|
||||||
|
Order matters: the first bound target is tried first, the second is fallback,
|
||||||
|
and so on. Typical next step: inspect with alias list, then run doctor.`,
|
||||||
|
Example: ` olpx alias bind --alias gpt-5.4 --provider su8 --model gpt-5.4
|
||||||
|
olpx alias bind --alias gpt-5.4 --provider codex --model GPT-5.4
|
||||||
|
olpx alias bind --alias gpt-5.4 --provider relay --model gpt-5.4 --disabled`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if alias == "" || provider == "" || model == "" {
|
if alias == "" || provider == "" || model == "" {
|
||||||
return fmt.Errorf("--alias, --provider and --model are required")
|
return fmt.Errorf("--alias, --provider and --model are required")
|
||||||
@ -144,6 +191,17 @@ func newAliasUnbindCmd() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "unbind",
|
Use: "unbind",
|
||||||
Short: "Remove a target from an alias",
|
Short: "Remove a target from an alias",
|
||||||
|
Long: `alias unbind removes one concrete provider/model target tuple from an alias in
|
||||||
|
local olpx config.
|
||||||
|
|
||||||
|
It does not delete the alias itself. Removing a target can leave the alias with
|
||||||
|
no routable targets, which doctor and opencode sync will then treat as invalid
|
||||||
|
or unavailable.
|
||||||
|
|
||||||
|
Typical next step: run alias list or doctor to confirm the remaining target
|
||||||
|
chain.`,
|
||||||
|
Example: ` olpx alias unbind --alias gpt-5.4 --provider codex --model GPT-5.4
|
||||||
|
olpx doctor`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if alias == "" || provider == "" || model == "" {
|
if alias == "" || provider == "" || model == "" {
|
||||||
return fmt.Errorf("--alias, --provider and --model are required")
|
return fmt.Errorf("--alias, --provider and --model are required")
|
||||||
@ -173,6 +231,16 @@ func newAliasRemoveCmd() *cobra.Command {
|
|||||||
Use: "remove <alias>",
|
Use: "remove <alias>",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Short: "Delete an alias entirely",
|
Short: "Delete an alias entirely",
|
||||||
|
Long: `alias remove deletes one alias and all of its target bindings from local olpx
|
||||||
|
config.
|
||||||
|
|
||||||
|
Future opencode sync runs will stop exposing that alias in provider.olpx.models.
|
||||||
|
This command does not directly clear top-level model selections that may still
|
||||||
|
reference the old alias in OpenCode config.
|
||||||
|
|
||||||
|
Typical next step: run olpx opencode sync if OpenCode exposure should be updated.`,
|
||||||
|
Example: ` olpx alias remove gpt-5.4
|
||||||
|
olpx opencode sync`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cfg, err := loadCfg()
|
cfg, err := loadCfg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -4,8 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/anomalyco/opencode-provider-switch/internal/config"
|
"github.com/anomalyco/opencode-provider-switch/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -220,3 +223,69 @@ func TestOpencodeSyncDoesNotPanicOnSliceModelMetadata(t *testing.T) {
|
|||||||
t.Fatalf("sync rewrote unchanged config:\n%s", string(data))
|
t.Fatalf("sync rewrote unchanged config:\n%s", string(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHelpTextIncludesOperationalGuidance(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cmd *cobra.Command
|
||||||
|
wantLong []string
|
||||||
|
wantExample []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "root",
|
||||||
|
cmd: NewRootCmd("test"),
|
||||||
|
wantLong: []string{"Typical workflow:", "prefer command-local --help over README summaries"},
|
||||||
|
wantExample: []string{"olpx provider add", "olpx serve"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "provider add",
|
||||||
|
cmd: newProviderAddCmd(),
|
||||||
|
wantLong: []string{"--base-url must point at an", "omitted mutable fields keep their current", "values: name, api key, headers"},
|
||||||
|
wantExample: []string{"olpx provider add --id relay", "--header X-Workspace=my-team"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "alias bind",
|
||||||
|
cmd: newAliasBindCmd(),
|
||||||
|
wantLong: []string{"auto-creates an enabled alias", "Order matters:"},
|
||||||
|
wantExample: []string{"--provider codex --model GPT-5.4", "--disabled"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "opencode sync",
|
||||||
|
cmd: newOpencodeSyncCmd(),
|
||||||
|
wantLong: []string{"does not follow", "writes alias", "exposure into provider.olpx.models"},
|
||||||
|
wantExample: []string{"--dry-run", "--set-small-model olpx/gpt-5.4-mini"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.cmd.Long == "" {
|
||||||
|
t.Fatal("Long help is empty")
|
||||||
|
}
|
||||||
|
if tt.cmd.Example == "" {
|
||||||
|
t.Fatal("Example help is empty")
|
||||||
|
}
|
||||||
|
for _, want := range tt.wantLong {
|
||||||
|
if !strings.Contains(tt.cmd.Long, want) {
|
||||||
|
t.Fatalf("Long help missing %q\n%s", want, tt.cmd.Long)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, want := range tt.wantExample {
|
||||||
|
if !strings.Contains(tt.cmd.Example, want) {
|
||||||
|
t.Fatalf("Example help missing %q\n%s", want, tt.cmd.Example)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
root := NewRootCmd("test")
|
||||||
|
flag := root.PersistentFlags().Lookup("config")
|
||||||
|
if flag == nil {
|
||||||
|
t.Fatal("--config flag not found")
|
||||||
|
}
|
||||||
|
for _, want := range []string{"$OLPX_CONFIG", "$XDG_CONFIG_HOME/olpx/config.json", "~/.config/olpx/config.json"} {
|
||||||
|
if !strings.Contains(flag.Usage, want) {
|
||||||
|
t.Fatalf("config flag usage missing %q: %s", want, flag.Usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,6 +12,18 @@ func newDoctorCmd() *cobra.Command {
|
|||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "doctor",
|
Use: "doctor",
|
||||||
Short: "Validate olpx config (static checks, no upstream requests)",
|
Short: "Validate olpx config (static checks, no upstream requests)",
|
||||||
|
Long: `doctor performs static validation for local olpx config and the expected
|
||||||
|
OpenCode sync result.
|
||||||
|
|
||||||
|
It loads local olpx config, checks alias/provider consistency, resolves the
|
||||||
|
default OpenCode sync target, and validates what provider.olpx would look like
|
||||||
|
there. It does not send any real requests to upstream providers and does not
|
||||||
|
consume model quota.
|
||||||
|
|
||||||
|
Run doctor before opencode sync or serve whenever you changed providers,
|
||||||
|
aliases, or local server settings.`,
|
||||||
|
Example: ` olpx doctor
|
||||||
|
olpx --config /path/to/config.json doctor`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cfg, err := loadCfg()
|
cfg, err := loadCfg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -12,6 +12,17 @@ func newOpencodeCmd() *cobra.Command {
|
|||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "opencode",
|
Use: "opencode",
|
||||||
Short: "OpenCode integration commands",
|
Short: "OpenCode integration commands",
|
||||||
|
Long: `OpenCode commands manage the narrow integration boundary between olpx and
|
||||||
|
OpenCode config.
|
||||||
|
|
||||||
|
These commands do not attempt full OpenCode config takeover. They are limited to
|
||||||
|
the provider.olpx sync path and optional top-level model fields when you opt in
|
||||||
|
explicitly.
|
||||||
|
|
||||||
|
Common workflow: validate with doctor first, inspect sync help, then run
|
||||||
|
opencode sync.`,
|
||||||
|
Example: ` olpx opencode sync --dry-run
|
||||||
|
olpx opencode sync --set-model olpx/gpt-5.4`,
|
||||||
}
|
}
|
||||||
c.AddCommand(newOpencodeSyncCmd())
|
c.AddCommand(newOpencodeSyncCmd())
|
||||||
return c
|
return c
|
||||||
@ -30,7 +41,21 @@ func newOpencodeSyncCmd() *cobra.Command {
|
|||||||
By default it targets the global user config (~/.config/opencode), picking the
|
By default it targets the global user config (~/.config/opencode), picking the
|
||||||
existing file in precedence order opencode.jsonc > opencode.json > config.json,
|
existing file in precedence order opencode.jsonc > opencode.json > config.json,
|
||||||
or creating opencode.jsonc if none exists. It does NOT touch the top-level
|
or creating opencode.jsonc if none exists. It does NOT touch the top-level
|
||||||
"model" or "small_model" unless --set-model / --set-small-model are given.`,
|
"model" or "small_model" unless --set-model / --set-small-model are given.
|
||||||
|
|
||||||
|
The default target scope is only the global user config path; it does not follow
|
||||||
|
OPENCODE_CONFIG_DIR unless you pass --target yourself. The command writes alias
|
||||||
|
exposure into provider.olpx.models using only aliases that are currently
|
||||||
|
routable.
|
||||||
|
|
||||||
|
Use --dry-run to preview the resolved target file without writing it. Typical
|
||||||
|
workflow: run olpx doctor first, then sync, then start or restart olpx serve if
|
||||||
|
needed.`,
|
||||||
|
Example: ` olpx opencode sync
|
||||||
|
olpx opencode sync --dry-run
|
||||||
|
olpx opencode sync --set-model olpx/gpt-5.4
|
||||||
|
olpx opencode sync --set-model olpx/gpt-5.4 --set-small-model olpx/gpt-5.4-mini
|
||||||
|
olpx opencode sync --target /path/to/opencode.jsonc`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cfg, err := loadCfg()
|
cfg, err := loadCfg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -15,6 +15,18 @@ func newProviderCmd() *cobra.Command {
|
|||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "provider",
|
Use: "provider",
|
||||||
Short: "Manage upstream providers",
|
Short: "Manage upstream providers",
|
||||||
|
Long: `Provider commands manage upstream OpenAI-compatible endpoints stored in the
|
||||||
|
local olpx config file.
|
||||||
|
|
||||||
|
Providers are separate from aliases: a provider defines connection details such
|
||||||
|
as base URL, API key, and extra headers, while aliases decide failover order by
|
||||||
|
binding one or more provider/model targets.
|
||||||
|
|
||||||
|
Common workflow: add or import providers first, inspect them with provider list,
|
||||||
|
then bind them to aliases with olpx alias bind.`,
|
||||||
|
Example: ` olpx provider add --id su8 --base-url https://cn2.su8.codes/v1 --api-key sk-example
|
||||||
|
olpx provider import-opencode
|
||||||
|
olpx provider list`,
|
||||||
}
|
}
|
||||||
c.AddCommand(
|
c.AddCommand(
|
||||||
newProviderAddCmd(),
|
newProviderAddCmd(),
|
||||||
@ -34,6 +46,23 @@ func newProviderAddCmd() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "add",
|
Use: "add",
|
||||||
Short: "Add or update an upstream provider",
|
Short: "Add or update an upstream provider",
|
||||||
|
Long: `provider add creates or updates one upstream provider entry in local olpx
|
||||||
|
config.
|
||||||
|
|
||||||
|
It writes only the olpx config file. --base-url must point at an
|
||||||
|
OpenAI-compatible /v1 root. The command validates that shape, but it does not
|
||||||
|
contact the upstream or test credentials.
|
||||||
|
|
||||||
|
When updating an existing provider, omitted mutable fields keep their current
|
||||||
|
values: name, api key, headers, and disabled state are preserved unless you
|
||||||
|
explicitly pass new values. Repeated --header KEY=VALUE entries replace the
|
||||||
|
stored header map for this command invocation.
|
||||||
|
|
||||||
|
Typical next step: run olpx provider list or bind the provider to an alias.`,
|
||||||
|
Example: ` olpx provider add --id su8 --base-url https://cn2.su8.codes/v1
|
||||||
|
olpx provider add --id su8 --base-url https://cn2.su8.codes/v1 --api-key sk-example
|
||||||
|
olpx provider add --id relay --base-url https://example.com/v1 --api-key sk-example --header X-Token=abc --header X-Workspace=my-team
|
||||||
|
olpx provider add --id su8 --base-url https://new.example.com/v1`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if id == "" || baseURL == "" {
|
if id == "" || baseURL == "" {
|
||||||
return fmt.Errorf("--id and --base-url are required")
|
return fmt.Errorf("--id and --base-url are required")
|
||||||
@ -111,6 +140,15 @@ func newProviderListCmd() *cobra.Command {
|
|||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List configured providers",
|
Short: "List configured providers",
|
||||||
|
Long: `provider list prints the providers currently stored in local olpx config.
|
||||||
|
|
||||||
|
Output is inspection-oriented: provider ids, enabled state, base URLs, and
|
||||||
|
redacted API keys are shown so you can confirm what was saved or imported before
|
||||||
|
binding aliases.
|
||||||
|
|
||||||
|
This command does not modify config and does not contact upstream providers.`,
|
||||||
|
Example: ` olpx provider list
|
||||||
|
olpx --config /path/to/config.json provider list`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cfg, err := loadCfg()
|
cfg, err := loadCfg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -155,6 +193,16 @@ func newProviderStateCmd(use string, disabled bool) *cobra.Command {
|
|||||||
Use: use + " <id>",
|
Use: use + " <id>",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Short: strings.Title(action[:len(action)-1]) + " a provider without changing alias target state",
|
Short: strings.Title(action[:len(action)-1]) + " a provider without changing alias target state",
|
||||||
|
Long: fmt.Sprintf(`provider %s flips one provider's disabled state in local olpx config.
|
||||||
|
|
||||||
|
It changes routing eligibility for every alias target that references this
|
||||||
|
provider, but it does not rewrite alias target enabled flags. This matters when
|
||||||
|
the same provider is shared across multiple aliases.
|
||||||
|
|
||||||
|
This command writes only the olpx config file and does not test upstream
|
||||||
|
reachability. Typical next step: run olpx doctor to confirm routable aliases.`, use),
|
||||||
|
Example: fmt.Sprintf(` olpx provider %s <id>
|
||||||
|
olpx doctor`, use),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cfg, err := loadCfg()
|
cfg, err := loadCfg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -181,6 +229,16 @@ func newProviderRemoveCmd() *cobra.Command {
|
|||||||
Use: "remove <id>",
|
Use: "remove <id>",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Short: "Remove a provider (targets referencing it must be removed first or will fail doctor)",
|
Short: "Remove a provider (targets referencing it must be removed first or will fail doctor)",
|
||||||
|
Long: `provider remove deletes one provider from local olpx config.
|
||||||
|
|
||||||
|
It does not automatically clean alias target references that still point at the
|
||||||
|
removed provider. If aliases still reference it, doctor will report invalid
|
||||||
|
config and those aliases will not be routable.
|
||||||
|
|
||||||
|
Typical follow-up: inspect aliases, unbind stale targets, then run olpx doctor.`,
|
||||||
|
Example: ` olpx provider remove su8
|
||||||
|
olpx alias list
|
||||||
|
olpx doctor`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cfg, err := loadCfg()
|
cfg, err := loadCfg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -204,6 +262,22 @@ func newProviderImportCmd() *cobra.Command {
|
|||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "import-opencode",
|
Use: "import-opencode",
|
||||||
Short: "Import @ai-sdk/openai custom providers from an OpenCode config file",
|
Short: "Import @ai-sdk/openai custom providers from an OpenCode config file",
|
||||||
|
Long: `provider import-opencode reads an OpenCode config file and copies supported
|
||||||
|
custom providers into local olpx config.
|
||||||
|
|
||||||
|
By default it reads the global user OpenCode config resolved in precedence order
|
||||||
|
opencode.jsonc > opencode.json > config.json under ~/.config/opencode (XDG
|
||||||
|
aware). It does not follow OPENCODE_CONFIG_DIR for this default source; use
|
||||||
|
--from when you want a different file.
|
||||||
|
|
||||||
|
Only config-defined @ai-sdk/openai custom providers with both baseURL and apiKey
|
||||||
|
are imported. Unsupported provider shapes are skipped by design. Existing olpx
|
||||||
|
providers are skipped unless --overwrite is given.
|
||||||
|
|
||||||
|
Typical next step: run olpx provider list, then create aliases and bindings.`,
|
||||||
|
Example: ` olpx provider import-opencode
|
||||||
|
olpx provider import-opencode --from /path/to/opencode.jsonc
|
||||||
|
olpx provider import-opencode --overwrite`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if srcPath == "" {
|
if srcPath == "" {
|
||||||
p, existed := opencode.ResolveGlobalConfigPath()
|
p, existed := opencode.ResolveGlobalConfigPath()
|
||||||
|
|||||||
@ -23,11 +23,34 @@ func NewRootCmd(version string) *cobra.Command {
|
|||||||
root := &cobra.Command{
|
root := &cobra.Command{
|
||||||
Use: "olpx",
|
Use: "olpx",
|
||||||
Short: "OpenCode LocalProxy CLI: local alias + failover proxy for OpenCode",
|
Short: "OpenCode LocalProxy CLI: local alias + failover proxy for OpenCode",
|
||||||
|
Long: `olpx is a local OpenCode proxy that exposes stable aliases as olpx/<alias>
|
||||||
|
while routing each alias to one or more upstream provider/model targets.
|
||||||
|
|
||||||
|
Typical workflow:
|
||||||
|
1. add or import upstream providers into local olpx config
|
||||||
|
2. create aliases and bind ordered targets
|
||||||
|
3. run doctor for static validation
|
||||||
|
4. run opencode sync to write provider.olpx into OpenCode config
|
||||||
|
5. run serve to accept local /v1/responses traffic
|
||||||
|
|
||||||
|
olpx writes only its own local config unless a command explicitly says it also
|
||||||
|
writes OpenCode config. For exact command behavior, defaults, and side effects,
|
||||||
|
prefer command-local --help over README summaries.`,
|
||||||
|
Example: ` olpx provider add --id su8 --base-url https://cn2.su8.codes/v1 --api-key sk-example
|
||||||
|
olpx alias add --name gpt-5.4 --display-name "GPT 5.4"
|
||||||
|
olpx alias bind --alias gpt-5.4 --provider su8 --model gpt-5.4
|
||||||
|
olpx doctor
|
||||||
|
olpx opencode sync
|
||||||
|
olpx serve
|
||||||
|
|
||||||
|
olpx provider import-opencode
|
||||||
|
olpx provider list
|
||||||
|
olpx opencode sync --dry-run`,
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
SilenceErrors: false,
|
SilenceErrors: false,
|
||||||
Version: version,
|
Version: version,
|
||||||
}
|
}
|
||||||
root.PersistentFlags().StringVar(&configPath, "config", "", "path to olpx config.json (default: $XDG_CONFIG_HOME/olpx/config.json)")
|
root.PersistentFlags().StringVar(&configPath, "config", "", "path to olpx config.json (default: $OLPX_CONFIG, else $XDG_CONFIG_HOME/olpx/config.json, else ~/.config/olpx/config.json)")
|
||||||
|
|
||||||
root.AddCommand(newServeCmd())
|
root.AddCommand(newServeCmd())
|
||||||
root.AddCommand(newDoctorCmd())
|
root.AddCommand(newDoctorCmd())
|
||||||
|
|||||||
@ -13,6 +13,17 @@ func newServeCmd() *cobra.Command {
|
|||||||
return &cobra.Command{
|
return &cobra.Command{
|
||||||
Use: "serve",
|
Use: "serve",
|
||||||
Short: "Run the local olpx proxy (alias -> failover upstream)",
|
Short: "Run the local olpx proxy (alias -> failover upstream)",
|
||||||
|
Long: `serve starts the long-running local olpx proxy using the current local config.
|
||||||
|
|
||||||
|
It reads local alias/provider configuration, validates that config, and then
|
||||||
|
accepts OpenAI Responses traffic at the configured local base URL. With default
|
||||||
|
settings the proxy listens on http://127.0.0.1:9982/v1 and expects the local API
|
||||||
|
key olpx-local.
|
||||||
|
|
||||||
|
serve does not rewrite config files. Run doctor and opencode sync first so
|
||||||
|
OpenCode can see the same aliases that the proxy can route.`,
|
||||||
|
Example: ` olpx serve
|
||||||
|
olpx --config /path/to/config.json serve`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cfg, err := loadCfg()
|
cfg, err := loadCfg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user