Skip to content

Commit 88e8189

Browse files
committed
feat: 0.6.1
1 parent ce806c9 commit 88e8189

36 files changed

Lines changed: 2800 additions & 130 deletions

.agent/skills/gate-cli-add-module/SKILL.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ Record exact field names and types — they often differ from what you'd guess (
8383

8484
## Step 4 — Scaffold Files
8585

86-
Create `cmd/<module>/` with one file per subgroup:
86+
Create `cmd/cex/<module>/` with one file per subgroup:
8787

8888
```
89-
cmd/<module>/
89+
cmd/cex/<module>/
9090
<module>.go — root Cmd only
9191
market.go — public endpoints (no auth)
9292
account.go — account/balance endpoints (auth)
@@ -237,21 +237,23 @@ return &Client{
237237
}
238238
```
239239

240-
**`cmd/root.go`** — register the command:
240+
**`cmd/cex/cex.go`** — register the command under the `cex` group:
241241

242242
```go
243-
import "github.com/gate/gate-cli/cmd/<module>"
244-
// ...
245-
rootCmd.AddCommand(<module>.Cmd)
243+
import "github.com/gate/gate-cli/cmd/cex/<module>"
244+
// in init():
245+
Cmd.AddCommand(<module>.Cmd)
246246
```
247247

248+
(`cmd/root.go` already adds `cex.Cmd`; do not register business modules on `rootCmd` directly.)
249+
248250
## Step 6 — Build and Test
249251

250252
```bash
251253
go build -o gate-cli .
252254
go test ./...
253-
./gate-cli <module> --help # verify group structure
254-
./gate-cli <module> market --help # verify leaf commands
255+
./gate-cli cex <module> --help # verify group structure
256+
./gate-cli cex <module> market --help # verify leaf commands
255257
```
256258

257259
Fix any field-name mismatches against actual SDK model files (the agent's guess about field names is often wrong — always verify).
@@ -272,7 +274,7 @@ Fix all issues, re-review if needed.
272274
When SDK was upgraded in the same session, include `go.mod`/`go.sum` in the module's commit:
273275

274276
```bash
275-
git add cmd/<module>/ cmd/root.go internal/client/client.go go.mod go.sum
277+
git add cmd/cex/cex.go cmd/cex/<module>/ internal/client/client.go go.mod go.sum
276278
git commit -m "feat(<module>): add <module> module with full API coverage
277279
278280
Adds N commands across M files:

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [0.6.1] - 2026-04-20
8+
9+
### Added
10+
11+
- **`internal/mcpspec/`** — new package that embeds the Info/News MCP spec JSON (`info-mcp-tools-inputs-logic.json`, `news-tools-args-and-logic.json`) for offline agent/LLM consumption. Bundled JSON is validated at init and kept byte-identical to `specs/mcp/*.json` via new parity tests (`internal/intelfacade/spec_baseline_parity_test.go`, `news_spec_baseline_parity_test.go`, `internal/mcpspec/spec_test.go`).
12+
- **`gate-cli info mcp-spec`** / **`gate-cli news mcp-spec`** — new leaf commands that print the embedded MCP inputs/spec document (tool names, fields, enums, bounds, logic text) with **no network call**. Table format is unsupported; use `--format json` or `pretty`.
13+
- **`GATE_INTEL_LEAF_HELP`** — leaf `--help` `Long` text now appends MCP-spec narrative (description / policy / logic / errors) via `mcpspec.InfoLeafLongAppend` / `NewsLeafLongAppend`. Default stays compact (cobra already lists flag type/default/enum/max); set `GATE_INTEL_LEAF_HELP=full` (or `detailed`) to embed per-field notes.
14+
- **`AnnotationIntelToolName`** (`gate-cli.intel.tool-name`) — cobra annotation attached to every intel leaf alias so tests and tooling can resolve the underlying MCP tool name.
15+
- **Intel `isError` helpers** — new `internal/intelcmd/intel_result_error_classify.go` and `is_error_message.go` with unit tests classify tool-side failures and extract a human-readable message from the MCP payload.
16+
17+
### Changed
18+
19+
- **Info / News baseline schemas** (`internal/intelfacade/info_schema_baseline.go`, `news_schema_baseline.go`) — enrich flat-flag help with `enum`, `default`, `minimum`, `maximum`, `maxLength`, `maxItems`, and `pattern` mirrored from `specs/mcp/*.json` (e.g. Info `query_type` / `scope` / `asset_type` / `sort_by` / `ranking_type` / `time_range` / `timeframe` / `period` / `indicators` / `source`; News `platform` / `domain` / `quality_tier` / `time_range` / `sort_by` / `limit` / `page` / `days` / `allowed_handles` / `excluded_handles`).
20+
- **`internal/toolschema`**`ApplyInputSchemaFlags` now coerces `default` from `float64` / `int` / `int64` / `json.Number` for `integer` and `number` fields; flag usage strings append `min`, `max`, `minLen`, `maxLen`, `minItems`, `maxItems`, `pattern` when defined. `verify.valueMatchesType` accepts `int` / `int64` alongside `float64` for integer/number values.
21+
- **`internal/toolrender/envelope`** — treat empty `structuredContent` (`{}`) as absent and fall back to `content[].text`; matches gateways that attach an empty structured payload.
22+
- **`internal/intelcmd/leaf_alias.go`**`LeafAliasConfig` gains `LongAppend`; the default `Long` block now documents `GATE_INTEL_LEAF_HELP=full` alongside the `--params` / `--args-json` / `--args-file` JSON-fallback flags.
23+
- **`internal/intelfacade/inventory.go`** — comment documents the News baseline at 8 tools (Info: 29, public gateway as of 2026-04).
24+
25+
### Fixed
26+
27+
- **Intel `isError` classification** (`internal/intelcmd/run_tool_call.go``GateErrorForIntelToolIsError`) — tool-side argument / validation failures now surface as **HTTP 400 + `INVALID_ARGUMENTS`** (based on 4xx `http_status` / `status_code` fields, well-known error codes such as `INVALID_ARGUMENT` / `BAD_REQUEST` / `VALIDATION_ERROR` / `OUT_OF_RANGE`, or snake_case `"<param> not supported"` wording). Transport / server errors stay at **502 + `INTEL_RESULT_ERROR`**, so scripts can distinguish caller-fixable input from backend outages.
28+
- **Intel error messages** — surface a trimmed, redacted summary (≤ 2048 runes) from `structuredContent` / `content[].text` / raw payload instead of the generic `"tool returned isError=true"`; bearer tokens in the message are redacted to `Bearer [redacted]`.
29+
730
## [0.6.0] - 2026-04-20
831

932
### Breaking
@@ -14,6 +37,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1437

1538
- **`cmd/cex/`** tree: `cex.go` wires every CEX domain package (`spot`, `futures`, `wallet`, `earn`, …) under **`gate-cli cex`**. Trading/account implementations now live under **`cmd/cex/<domain>/`** (moved from the former top-level **`cmd/<domain>/`** layout).
1639
- **Skill / MCP alignment docs** (for mapping Exchange skills and legacy MCP names to CLI):
40+
- `cmd/cex/GATE_EXCHANGE_SKILLS_MCP_TO_GATE_CLI.md` — skill token → `gate-cli` invocation
41+
- `cmd/cex/MCP_LEGACY_TOOL_RESOLUTION.md` — legacy `cex_*` tool resolution
42+
- `cmd/cex/COMMAND_API_MAP.md` — API ↔ command reference
43+
- **`bin/`** — prebuilt `gate-cli` binaries for **darwin / linux** (**arm64**, **x86_64**) for bundled or offline installs (e.g. skills / OpenClaw workflows).
1744

1845
### Changed
1946

cmd/info/aliases.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/gate/gate-cli/internal/intelcmd"
77
"github.com/gate/gate-cli/internal/intelfacade"
8+
"github.com/gate/gate-cli/internal/mcpspec"
89
"github.com/gate/gate-cli/internal/toolschema"
910
)
1011

@@ -13,6 +14,7 @@ func makeInfoAliasCommand(use, toolName string) *cobra.Command {
1314
BackendCLI: "info",
1415
Use: use,
1516
ToolName: toolName,
17+
LongAppend: mcpspec.InfoLeafLongAppend(toolName),
1618
RunE: func(cmd *cobra.Command, args []string) error {
1719
return runInfoCallByName(cmd, toolName, intelcmd.ReservedMCPJSONFallbackFlags())
1820
},

cmd/info/aliases_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66

77
"github.com/spf13/cobra"
88

9+
"github.com/gate/gate-cli/internal/intelcmd"
910
"github.com/gate/gate-cli/internal/intelfacade"
11+
"github.com/gate/gate-cli/internal/mcpspec"
1012
"github.com/gate/gate-cli/internal/toolschema"
1113
)
1214

@@ -65,3 +67,114 @@ func TestInfoGetCoinInfoHasStaticFlatFlagsWhenLoaderEmpty(t *testing.T) {
6567
}
6668
}
6769
}
70+
71+
func TestInfoIntelLeafToolAnnotation(t *testing.T) {
72+
for _, tool := range intelfacade.InfoToolBaseline {
73+
parts := strings.Split(tool, "_")
74+
if len(parts) < 3 {
75+
t.Fatalf("invalid tool %q", tool)
76+
}
77+
group := parts[1]
78+
leaf := strings.Join(parts[2:], "-")
79+
leafCmd, _, err := Cmd.Find([]string{group, leaf})
80+
if err != nil || leafCmd == nil {
81+
t.Fatalf("find %s/%s for %q: %v", group, leaf, tool, err)
82+
}
83+
if got := leafCmd.Annotations[intelcmd.AnnotationIntelToolName]; got != tool {
84+
t.Fatalf("%s/%s: annotation %q want %q", group, leaf, got, tool)
85+
}
86+
}
87+
}
88+
89+
// TestInfoEachLeafRegistersAllBaselineFlags ensures every baseline JSON-schema property
90+
// is wired as a cobra flag on the matching leaf (static wiring; empty schema tools skip).
91+
func TestInfoEachLeafRegistersAllBaselineFlags(t *testing.T) {
92+
oldLoader := infoSchemaLoader
93+
infoSchemaLoader = func() map[string]toolschema.ToolSummary { return map[string]toolschema.ToolSummary{} }
94+
t.Cleanup(func() { infoSchemaLoader = oldLoader })
95+
96+
cmd := &cobra.Command{Use: "info"}
97+
orig := Cmd
98+
Cmd = cmd
99+
t.Cleanup(func() { Cmd = orig })
100+
buildInfoAliases()
101+
102+
for _, tool := range intelfacade.InfoToolBaseline {
103+
parts := strings.Split(tool, "_")
104+
group := parts[1]
105+
leaf := strings.Join(parts[2:], "-")
106+
leafCmd, _, err := cmd.Find([]string{group, leaf})
107+
if err != nil || leafCmd == nil {
108+
t.Fatalf("find %s/%s for %q: %v", group, leaf, tool, err)
109+
}
110+
schema := intelfacade.InfoBaselineInputSchema(tool)
111+
if schema == nil {
112+
t.Fatalf("nil baseline schema for %q", tool)
113+
}
114+
props, ok := schema["properties"].(map[string]interface{})
115+
if !ok {
116+
t.Fatalf("%q: missing properties", tool)
117+
}
118+
for k := range props {
119+
flagName := strings.ReplaceAll(k, "_", "-")
120+
if leafCmd.Flags().Lookup(flagName) == nil {
121+
t.Errorf("tool %q missing flag --%s (baseline key %q)", tool, flagName, k)
122+
}
123+
}
124+
}
125+
}
126+
127+
// TestInfoEachLeafRegistersAllSpecFields ensures embedded MCP spec fields are present as flags
128+
// (belt-and-suspenders on top of intelfacade.TestInfoBaselineCoversSpecInputFields).
129+
func TestInfoEachLeafRegistersAllSpecFields(t *testing.T) {
130+
doc, err := mcpspec.InfoInputsLogic()
131+
if err != nil {
132+
t.Fatal(err)
133+
}
134+
root := doc.(map[string]interface{})
135+
raw := root["tools"].([]interface{})
136+
specByTool := make(map[string][]string, len(raw))
137+
for _, item := range raw {
138+
tm := item.(map[string]interface{})
139+
name, _ := tm["tool_name"].(string)
140+
if name == "" {
141+
continue
142+
}
143+
fields, _ := tm["fields"].([]interface{})
144+
for _, f := range fields {
145+
fm := f.(map[string]interface{})
146+
if n, _ := fm["name"].(string); n != "" {
147+
specByTool[name] = append(specByTool[name], n)
148+
}
149+
}
150+
}
151+
152+
oldLoader := infoSchemaLoader
153+
infoSchemaLoader = func() map[string]toolschema.ToolSummary { return map[string]toolschema.ToolSummary{} }
154+
t.Cleanup(func() { infoSchemaLoader = oldLoader })
155+
cmd := &cobra.Command{Use: "info"}
156+
orig := Cmd
157+
Cmd = cmd
158+
t.Cleanup(func() { Cmd = orig })
159+
buildInfoAliases()
160+
161+
for _, tool := range intelfacade.InfoToolBaseline {
162+
names := specByTool[tool]
163+
if len(names) == 0 {
164+
continue
165+
}
166+
parts := strings.Split(tool, "_")
167+
group := parts[1]
168+
leaf := strings.Join(parts[2:], "-")
169+
leafCmd, _, err := cmd.Find([]string{group, leaf})
170+
if err != nil || leafCmd == nil {
171+
t.Fatalf("find %s/%s for %q: %v", group, leaf, tool, err)
172+
}
173+
for _, field := range names {
174+
flagName := strings.ReplaceAll(field, "_", "-")
175+
if leafCmd.Flags().Lookup(flagName) == nil {
176+
t.Errorf("tool %q missing flag for spec field %q (--%s)", tool, field, flagName)
177+
}
178+
}
179+
}
180+
}

cmd/info/mcp_spec.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package info
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/gate/gate-cli/internal/intelcmd"
7+
"github.com/gate/gate-cli/internal/mcpspec"
8+
)
9+
10+
var mcpSpecCmd = &cobra.Command{
11+
Use: "mcp-spec",
12+
Short: "Print embedded Info MCP inputs/spec JSON (offline, for agents and LLMs)",
13+
Long: "Outputs the same document as specs/mcp/info-mcp-tools-inputs-logic.json bundled in the binary: " +
14+
"tool names, fields, enums, default/max bounds, and logic text. No MCP network call; use with --format json or pretty. " +
15+
"When specs change, copy into internal/mcpspec/bundled/ so tests and embed stay in sync.",
16+
Args: cobra.NoArgs,
17+
RunE: runInfoMCPSpec,
18+
}
19+
20+
func init() {
21+
Cmd.AddCommand(mcpSpecCmd)
22+
}
23+
24+
func runInfoMCPSpec(cmd *cobra.Command, args []string) error {
25+
p := getPrinter(cmd)
26+
if p.IsTable() {
27+
return intelcmd.FailLeafUnsupportedTable(p, "info")
28+
}
29+
doc, err := mcpspec.InfoInputsLogic()
30+
if err != nil {
31+
return err
32+
}
33+
return p.Print(doc)
34+
}

cmd/info/mcp_spec_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package info
2+
3+
import "testing"
4+
5+
func TestInfoMCPSpecCommandRegistered(t *testing.T) {
6+
if Cmd == nil {
7+
t.Fatal("Cmd is nil")
8+
}
9+
sub, _, err := Cmd.Find([]string{"mcp-spec"})
10+
if err != nil || sub == nil || sub.Name() != "mcp-spec" {
11+
t.Fatalf("mcp-spec subcommand: err=%v cmd=%v", err, sub)
12+
}
13+
}

cmd/news/aliases.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/gate/gate-cli/internal/intelcmd"
77
"github.com/gate/gate-cli/internal/intelfacade"
8+
"github.com/gate/gate-cli/internal/mcpspec"
89
"github.com/gate/gate-cli/internal/toolschema"
910
)
1011

@@ -13,6 +14,7 @@ func makeNewsAliasCommand(use, toolName string) *cobra.Command {
1314
BackendCLI: "news",
1415
Use: use,
1516
ToolName: toolName,
17+
LongAppend: mcpspec.NewsLeafLongAppend(toolName),
1618
RunE: func(cmd *cobra.Command, args []string) error {
1719
return runNewsCallByName(cmd, toolName, intelcmd.ReservedMCPJSONFallbackFlags())
1820
},

0 commit comments

Comments
 (0)