@@ -18,10 +18,12 @@ import (
1818 state "github.com/jguan/aima/internal"
1919 "github.com/jguan/aima/internal/agent"
2020 benchpkg "github.com/jguan/aima/internal/benchmark"
21+ "github.com/jguan/aima/internal/cli"
2122 "github.com/jguan/aima/internal/engine"
2223 "github.com/jguan/aima/internal/knowledge"
2324 "github.com/jguan/aima/internal/mcp"
2425 aimaRuntime "github.com/jguan/aima/internal/runtime"
26+ "github.com/spf13/cobra"
2527)
2628
2729type fakeRuntime struct {
@@ -50,6 +52,29 @@ type mockCommandRunner struct {
5052 pipe func (context.Context , []string , []string ) error
5153}
5254
55+ type onboardingManifestDoc struct {
56+ Locales map [string ]* onboardingLocaleDoc `json:"locales"`
57+ }
58+
59+ type onboardingLocaleDoc struct {
60+ FullCommands onboardingFullCommandsDoc `json:"full_commands"`
61+ }
62+
63+ type onboardingFullCommandsDoc struct {
64+ Groups []onboardingGroupDoc `json:"groups"`
65+ }
66+
67+ type onboardingGroupDoc struct {
68+ ID string `json:"id"`
69+ Items []onboardingCommandDoc `json:"items"`
70+ }
71+
72+ type onboardingCommandDoc struct {
73+ ID string `json:"id"`
74+ Command string `json:"command"`
75+ Description string `json:"description"`
76+ }
77+
5378func (m * mockCommandRunner ) Run (ctx context.Context , name string , args ... string ) ([]byte , error ) {
5479 if m .run != nil {
5580 return m .run (ctx , name , args ... )
@@ -106,6 +131,235 @@ func TestOnboardingManifestEmbeddedShape(t *testing.T) {
106131 }
107132}
108133
134+ func TestBuildOnboardingManifestJSON_IncludesAllTopLevelCommands (t * testing.T ) {
135+ t .Parallel ()
136+
137+ raw , err := buildOnboardingManifestJSON (& knowledge.Catalog {})
138+ if err != nil {
139+ t .Fatalf ("build onboarding manifest: %v" , err )
140+ }
141+
142+ manifest := decodeOnboardingManifest (t , raw )
143+ zh , ok := manifest .Locales ["zh" ]
144+ if ! ok || zh == nil {
145+ t .Fatalf ("zh locale missing: %#v" , manifest .Locales )
146+ }
147+
148+ topLevel := findOnboardingGroup (t , zh .FullCommands .Groups , "top_level_commands" )
149+
150+ root := cli .NewRootCmd (& cli.App {})
151+ root .InitDefaultHelpCmd ()
152+ root .InitDefaultCompletionCmd ()
153+
154+ want := make (map [string ]struct {})
155+ for _ , cmd := range root .Commands () {
156+ if cmd == nil || cmd .Hidden {
157+ continue
158+ }
159+ want ["/cli " + cmd .Name ()] = struct {}{}
160+ }
161+
162+ got := make (map [string ]struct {})
163+ for _ , item := range topLevel .Items {
164+ got [item .Command ] = struct {}{}
165+ }
166+
167+ if len (got ) != len (want ) {
168+ t .Fatalf ("top_level_commands item count = %d, want %d\n items=%#v" , len (got ), len (want ), topLevel .Items )
169+ }
170+
171+ for command := range want {
172+ if _ , ok := got [command ]; ! ok {
173+ t .Fatalf ("top_level_commands missing command %s\n items=%#v" , command , topLevel .Items )
174+ }
175+ }
176+
177+ for command := range got {
178+ if _ , ok := want [command ]; ! ok {
179+ t .Fatalf ("top_level_commands has unexpected command %s\n items=%#v" , command , topLevel .Items )
180+ }
181+ }
182+ }
183+
184+ func TestBuildOnboardingManifestJSON_MarksSampleModelExamplesAsReplaceable (t * testing.T ) {
185+ t .Parallel ()
186+
187+ cat := & knowledge.Catalog {
188+ ModelAssets : []knowledge.ModelAsset {
189+ {
190+ Metadata : knowledge.ModelMetadata {
191+ Name : "demo-llm" ,
192+ Type : "llm" ,
193+ Family : "demo" ,
194+ ParameterCount : "1B" ,
195+ },
196+ },
197+ },
198+ }
199+
200+ raw , err := buildOnboardingManifestJSON (cat )
201+ if err != nil {
202+ t .Fatalf ("build onboarding manifest: %v" , err )
203+ }
204+
205+ text := string (raw )
206+ for _ , want := range []string {
207+ `"/cli help"` ,
208+ `"/cli model pull demo-llm"` ,
209+ `"/cli deploy demo-llm --dry-run"` ,
210+ `"/cli run demo-llm"` ,
211+ "demo-llm 是示例模型名,可替换成你自己的模型名" ,
212+ "demo-llm is an example model name; replace it with your own model name" ,
213+ } {
214+ if ! strings .Contains (text , want ) {
215+ t .Fatalf ("generated manifest missing %q\n manifest=%s" , want , text )
216+ }
217+ }
218+ }
219+
220+ func TestBuildOnboardingManifestJSON_UsesLocalizedTemplates (t * testing.T ) {
221+ t .Parallel ()
222+
223+ sourceRaw , err := catalog .FS .ReadFile ("ui-onboarding.json" )
224+ if err != nil {
225+ t .Fatalf ("read embedded onboarding manifest: %v" , err )
226+ }
227+
228+ source := decodeOnboardingManifest (t , sourceRaw )
229+ zhSource , ok := source .Locales ["zh" ]
230+ if ! ok || zhSource == nil {
231+ t .Fatalf ("source zh locale missing: %#v" , source .Locales )
232+ }
233+ enSource , ok := source .Locales ["en" ]
234+ if ! ok || enSource == nil {
235+ t .Fatalf ("source en locale missing: %#v" , source .Locales )
236+ }
237+
238+ zhTemplates := findOnboardingGroup (t , zhSource .FullCommands .Groups , "top_level_commands" )
239+ enTemplates := findOnboardingGroup (t , enSource .FullCommands .Groups , "top_level_commands" )
240+ zhHelpTemplate := findOnboardingCommandByID (t , zhTemplates .Items , "help" )
241+ zhModelTemplate := findOnboardingCommandByID (t , zhTemplates .Items , "model" )
242+ zhDeployTemplate := findOnboardingCommandByID (t , zhTemplates .Items , "deploy" )
243+ enHelpTemplate := findOnboardingCommandByID (t , enTemplates .Items , "help" )
244+
245+ for _ , template := range []onboardingCommandDoc {zhHelpTemplate , zhModelTemplate , zhDeployTemplate , enHelpTemplate } {
246+ if strings .TrimSpace (template .Description ) == "" {
247+ t .Fatalf ("top_level_commands template missing description: %#v" , template )
248+ }
249+ }
250+
251+ raw , err := buildOnboardingManifestJSON (& knowledge.Catalog {
252+ ModelAssets : []knowledge.ModelAsset {
253+ {
254+ Metadata : knowledge.ModelMetadata {
255+ Name : "demo-llm" ,
256+ Type : "llm" ,
257+ Family : "demo" ,
258+ ParameterCount : "1B" ,
259+ },
260+ },
261+ },
262+ })
263+ if err != nil {
264+ t .Fatalf ("build onboarding manifest: %v" , err )
265+ }
266+
267+ manifest := decodeOnboardingManifest (t , raw )
268+ zh := manifest .Locales ["zh" ]
269+ en := manifest .Locales ["en" ]
270+ if zh == nil || en == nil {
271+ t .Fatalf ("missing locales: %#v" , manifest .Locales )
272+ }
273+
274+ zhHelp := findOnboardingCommandDescription (t , zh .FullCommands .Groups , "top_level_commands" , "/cli help" )
275+ if want := replaceSampleModelPlaceholder (zhHelpTemplate .Description , "demo-llm" ); zhHelp != want {
276+ t .Fatalf ("zh /cli help description = %q, want %q" , zhHelp , want )
277+ }
278+
279+ enHelp := findOnboardingCommandDescription (t , en .FullCommands .Groups , "top_level_commands" , "/cli help" )
280+ if want := replaceSampleModelPlaceholder (enHelpTemplate .Description , "demo-llm" ); enHelp != want {
281+ t .Fatalf ("en /cli help description = %q, want %q" , enHelp , want )
282+ }
283+
284+ zhModel := findOnboardingCommandDescription (t , zh .FullCommands .Groups , "top_level_commands" , "/cli model" )
285+ if want := replaceSampleModelPlaceholder (zhModelTemplate .Description , "demo-llm" ); zhModel != want {
286+ t .Fatalf ("zh /cli model description = %q, want %q" , zhModel , want )
287+ }
288+
289+ zhDeploy := findOnboardingCommandDescription (t , zh .FullCommands .Groups , "top_level_commands" , "/cli deploy" )
290+ if want := replaceSampleModelPlaceholder (zhDeployTemplate .Description , "demo-llm" ); zhDeploy != want {
291+ t .Fatalf ("zh /cli deploy description = %q, want %q" , zhDeploy , want )
292+ }
293+ }
294+
295+ func TestBuildTopLevelOnboardingItems_FallsBackToCommandShort (t * testing.T ) {
296+ t .Parallel ()
297+
298+ root := & cobra.Command {Use : "aima" }
299+ root .AddCommand (& cobra.Command {
300+ Use : "version" ,
301+ Short : "Show version information" ,
302+ })
303+
304+ items := buildTopLevelOnboardingItems (root , map [string ]string {})
305+ if len (items ) != 1 {
306+ t .Fatalf ("buildTopLevelOnboardingItems() item count = %d, want 1" , len (items ))
307+ }
308+ if got := items [0 ]["description" ]; got != "Show version information" {
309+ t .Fatalf ("buildTopLevelOnboardingItems() description = %#v, want %q" , got , "Show version information" )
310+ }
311+ }
312+
313+ func decodeOnboardingManifest (t * testing.T , raw []byte ) onboardingManifestDoc {
314+ t .Helper ()
315+
316+ var manifest onboardingManifestDoc
317+ if err := json .Unmarshal (raw , & manifest ); err != nil {
318+ t .Fatalf ("unmarshal onboarding manifest: %v" , err )
319+ }
320+ return manifest
321+ }
322+
323+ func findOnboardingGroup (t * testing.T , groups []onboardingGroupDoc , groupID string ) onboardingGroupDoc {
324+ t .Helper ()
325+
326+ for _ , group := range groups {
327+ if group .ID == groupID {
328+ return group
329+ }
330+ }
331+
332+ t .Fatalf ("group %q not found" , groupID )
333+ return onboardingGroupDoc {}
334+ }
335+
336+ func findOnboardingCommandByID (t * testing.T , items []onboardingCommandDoc , id string ) onboardingCommandDoc {
337+ t .Helper ()
338+
339+ for _ , item := range items {
340+ if item .ID == id {
341+ return item
342+ }
343+ }
344+
345+ t .Fatalf ("command id %q not found" , id )
346+ return onboardingCommandDoc {}
347+ }
348+
349+ func findOnboardingCommandDescription (t * testing.T , groups []onboardingGroupDoc , groupID , command string ) string {
350+ t .Helper ()
351+
352+ group := findOnboardingGroup (t , groups , groupID )
353+ for _ , item := range group .Items {
354+ if item .Command == command {
355+ return item .Description
356+ }
357+ }
358+
359+ t .Fatalf ("command %q not found in group %q" , command , groupID )
360+ return ""
361+ }
362+
109363func TestParseExtraParamsStrict (t * testing.T ) {
110364 tests := []struct {
111365 name string
0 commit comments