@@ -88,10 +88,11 @@ func symArrow() string {
8888
8989// UpdateOptions holds inputs for the update command.
9090type UpdateOptions struct {
91- Factory * cmdutil.Factory
92- JSON bool
93- Force bool
94- Check bool
91+ Factory * cmdutil.Factory
92+ JSON bool
93+ Force bool
94+ Check bool
95+ SkillsLayout string
9596}
9697
9798// NewCmdUpdate creates the update command.
@@ -109,7 +110,9 @@ Detects the installation method automatically:
109110 - manual/other: shows GitHub Releases download URL
110111
111112Use --json for structured output (for AI agents and scripts).
112- Use --check to only check for updates without installing.` ,
113+ Use --check to only check for updates without installing.
114+
115+ The skill name "lark-suite" is reserved for CLI-managed suite layout.` ,
113116 RunE : func (cmd * cobra.Command , args []string ) error {
114117 return updateRun (opts )
115118 },
@@ -118,13 +121,24 @@ Use --check to only check for updates without installing.`,
118121 cmd .Flags ().BoolVar (& opts .JSON , "json" , false , "structured JSON output" )
119122 cmd .Flags ().BoolVar (& opts .Force , "force" , false , "force reinstall even if already up to date" )
120123 cmd .Flags ().BoolVar (& opts .Check , "check" , false , "only check for updates, do not install" )
124+ cmd .Flags ().StringVar (& opts .SkillsLayout , "skills-layout" , "" , "skills layout: separate or suite" )
121125 cmdutil .SetRisk (cmd , "high-risk-write" )
122126
123127 return cmd
124128}
125129
126130func updateRun (opts * UpdateOptions ) error {
127131 io := opts .Factory .IOStreams
132+ if _ , err := skillscheck .ParseLayout (opts .SkillsLayout ); err != nil {
133+ return reportError (opts , io , "validation" ,
134+ errs .NewValidationError (errs .SubtypeInvalidArgument , "--skills-layout must be one of separate or suite" ).WithParam ("--skills-layout" ))
135+ }
136+ if opts .Check && strings .TrimSpace (opts .SkillsLayout ) != "" {
137+ return reportError (opts , io , "validation" ,
138+ errs .NewValidationError (errs .SubtypeInvalidArgument , "--skills-layout cannot be used with --check" ).
139+ WithParam ("--skills-layout" ).
140+ WithHint ("Remove --skills-layout when using --check." ))
141+ }
128142 cur := currentVersion ()
129143 updater := newUpdater ()
130144 // Brand only steers skills sync. updateRun skips that resolution in --check,
@@ -152,7 +166,10 @@ func updateRun(opts *UpdateOptions) error {
152166 if ! opts .Force && ! update .IsNewer (latest , cur ) {
153167 var skillsResult * skillscheck.SyncResult
154168 if ! opts .Check {
155- skillsResult = runSkillsAndState (updater , io , cur , opts .Force )
169+ skillsResult = runSkillsAndState (updater , io , cur , opts .Force , opts .SkillsLayout )
170+ if err := reportSkillsFailure (opts , io , skillsResult ); err != nil {
171+ return err
172+ }
156173 }
157174 return reportAlreadyUpToDate (opts , io , cur , latest , skillsResult , opts .Check )
158175 }
@@ -195,10 +212,18 @@ func resolveSkillsBrand(f *cmdutil.Factory, errOut stdio.Writer) core.LarkBrand
195212// error's exit code bare; human mode returns the typed error for the
196213// dispatcher to render.
197214func reportError (opts * UpdateOptions , io * cmdutil.IOStreams , errType string , typedErr errs.TypedError ) error {
215+ return reportErrorWithFields (opts , io , errType , typedErr , nil )
216+ }
217+
218+ func reportErrorWithFields (opts * UpdateOptions , io * cmdutil.IOStreams , errType string , typedErr errs.TypedError , fields map [string ]interface {}) error {
198219 if opts .JSON {
199- output .PrintJson (io .Out , map [string ]interface {}{
200- "ok" : false , "error" : map [string ]interface {}{"type" : errType , "message" : typedErr .ProblemDetail ().Message },
201- })
220+ out := make (map [string ]interface {}, len (fields )+ 2 )
221+ for key , value := range fields {
222+ out [key ] = value
223+ }
224+ out ["ok" ] = false
225+ out ["error" ] = map [string ]interface {}{"type" : errType , "message" : typedErr .ProblemDetail ().Message }
226+ output .PrintJson (io .Out , out )
202227 return output .ErrBare (output .ExitCodeOf (typedErr ))
203228 }
204229 return typedErr
@@ -229,8 +254,7 @@ func reportCheckResult(opts *UpdateOptions, io *cmdutil.IOStreams, cur, latest s
229254}
230255
231256func doManualUpdate (opts * UpdateOptions , io * cmdutil.IOStreams , cur , latest string , detect selfupdate.DetectResult , updater * selfupdate.Updater ) error {
232- skillsResult := runSkillsAndState (updater , io , cur , opts .Force )
233-
257+ skillsResult := runSkillsAndState (updater , io , cur , opts .Force , opts .SkillsLayout )
234258 reason := detect .ManualReason ()
235259 if opts .JSON {
236260 out := map [string ]interface {}{
@@ -240,6 +264,9 @@ func doManualUpdate(opts *UpdateOptions, io *cmdutil.IOStreams, cur, latest stri
240264 "url" : releaseURL (latest ), "changelog" : changelogURL (),
241265 }
242266 applySkillsResult (out , skillsResult )
267+ if err := reportSkillsFailureWithFields (opts , io , skillsResult , out ); err != nil {
268+ return err
269+ }
243270 output .PrintJson (io .Out , out )
244271 return nil
245272 }
@@ -252,6 +279,9 @@ func doManualUpdate(opts *UpdateOptions, io *cmdutil.IOStreams, cur, latest stri
252279 } else {
253280 fmt .Fprintf (io .ErrOut , "\n Or install via npm (note: skills will not be synced):\n npm install -g %s@%s\n npx skills add larksuite/cli -y -g # sync skills separately\n " , selfupdate .NpmPackage , latest )
254281 }
282+ if err := reportSkillsFailure (opts , io , skillsResult ); err != nil {
283+ return err
284+ }
255285 emitSkillsTextHints (io , skillsResult )
256286 return nil
257287}
@@ -319,7 +349,21 @@ func doAutoUpdate(opts *UpdateOptions, io *cmdutil.IOStreams, cur, latest string
319349 return output .ErrBare (output .ExitAPI )
320350 }
321351
322- skillsResult := runSkillsAndState (updater , io , latest , opts .Force )
352+ skillsResult := runSkillsAndState (updater , io , latest , opts .Force , opts .SkillsLayout )
353+ if skillsResult != nil && skillsResult .Err != nil {
354+ fields := map [string ]interface {}{
355+ "previous_version" : cur , "current_version" : latest ,
356+ "latest_version" : latest , "action" : "updated" ,
357+ "message" : fmt .Sprintf ("lark-cli updated from %s to %s, but skills update failed" , cur , latest ),
358+ "url" : releaseURL (latest ), "changelog" : changelogURL (),
359+ }
360+ applySkillsResult (fields , skillsResult )
361+ if ! opts .JSON {
362+ fmt .Fprintf (io .ErrOut , "\n %s lark-cli binary updated from %s to %s\n " , symOK (), cur , latest )
363+ fmt .Fprintf (io .ErrOut , " Changelog: %s\n " , changelogURL ())
364+ }
365+ return reportSkillsFailureWithFields (opts , io , skillsResult , fields )
366+ }
323367
324368 if opts .JSON {
325369 result := map [string ]interface {}{
@@ -366,14 +410,18 @@ func verificationFailureHint(updater *selfupdate.Updater, latest, pm string) str
366410 return fmt .Sprintf ("automatic rollback is unavailable on this platform; reinstall manually (skills will not be synced): npm install -g %s@%s && npx skills add larksuite/cli -y -g, or download %s" , selfupdate .NpmPackage , latest , releaseURL (latest ))
367411}
368412
369- func runSkillsAndState (updater * selfupdate.Updater , io * cmdutil.IOStreams , stateVersion string , force bool ) * skillscheck.SyncResult {
413+ func runSkillsAndState (updater * selfupdate.Updater , io * cmdutil.IOStreams , stateVersion string , force bool , requestedLayout string ) * skillscheck.SyncResult {
414+ layout , _ := skillscheck .ParseLayout (requestedLayout )
370415 if ! force {
371- if existing , ok := skillscheck .ReadSyncedVersion (); ok && normalizeVersion (existing ) == normalizeVersion (stateVersion ) {
372- return nil
416+ if state , ok , err := skillscheck .ReadState (); err == nil && ok && normalizeVersion (state .Version ) == normalizeVersion (stateVersion ) {
417+ if ! state .OfficialSkillsUnknown && (layout == "" || skillscheck .EffectiveLayout (state ) == layout ) {
418+ return nil
419+ }
373420 }
374421 }
375422 result := syncSkills (skillscheck.SyncOptions {
376423 Version : stateVersion ,
424+ Layout : layout ,
377425 Force : force ,
378426 Runner : updater ,
379427 })
@@ -383,6 +431,20 @@ func runSkillsAndState(updater *selfupdate.Updater, io *cmdutil.IOStreams, state
383431 return result
384432}
385433
434+ func reportSkillsFailure (opts * UpdateOptions , io * cmdutil.IOStreams , result * skillscheck.SyncResult ) error {
435+ return reportSkillsFailureWithFields (opts , io , result , nil )
436+ }
437+
438+ func reportSkillsFailureWithFields (opts * UpdateOptions , io * cmdutil.IOStreams , result * skillscheck.SyncResult , fields map [string ]interface {}) error {
439+ if result == nil || result .Err == nil {
440+ return nil
441+ }
442+ typedErr := errs .NewInternalError (errs .SubtypeUnknown , "skills update failed: %s" , result .Err ).
443+ WithHint ("retry with `lark-cli update --force`" ).
444+ WithCause (result .Err )
445+ return reportErrorWithFields (opts , io , "skills_update_error" , typedErr , fields )
446+ }
447+
386448// reportAlreadyUpToDate emits the JSON / pretty output for the
387449// already-up-to-date branch, including any skills_action / skills_warning
388450// fields derived from skillsResult. When check is true, this is the pure
@@ -418,9 +480,11 @@ func applySkillsStatus(env map[string]interface{}, target string) {
418480 status := map [string ]interface {}{
419481 "current" : state .Version ,
420482 "target" : target ,
421- "in_sync" : normalizeVersion (state .Version ) == normalizeVersion (target ),
483+ "in_sync" : normalizeVersion (state .Version ) == normalizeVersion (target ) && ! state . OfficialSkillsUnknown ,
422484 }
423- if len (state .OfficialSkills ) > 0 {
485+ if state .OfficialSkillsUnknown {
486+ status ["official_unknown" ] = true
487+ } else if len (state .OfficialSkills ) > 0 {
424488 status ["official" ] = len (state .OfficialSkills )
425489 }
426490 if len (state .UpdatedSkills ) > 0 {
@@ -429,6 +493,7 @@ func applySkillsStatus(env map[string]interface{}, target string) {
429493 if len (state .SkippedDeletedSkills ) > 0 {
430494 status ["skipped_deleted" ] = state .SkippedDeletedSkills
431495 }
496+ status ["layout" ] = skillscheck .EffectiveLayout (state )
432497 env ["skills_status" ] = status
433498}
434499
@@ -443,15 +508,23 @@ func applySkillsResult(env map[string]interface{}, r *skillscheck.SyncResult) {
443508 default :
444509 env ["skills_action" ] = "synced"
445510 env ["skills_summary" ] = skillsSummary (r )
511+ if r .Warning != "" {
512+ env ["skills_warning" ] = r .Warning
513+ }
446514 }
447515}
448516
449517func skillsSummary (r * skillscheck.SyncResult ) map [string ]interface {} {
450518 summary := map [string ]interface {}{
451- "official" : len (r .Official ),
452519 "updated" : len (r .Updated ),
453520 "added" : len (r .Added ),
454521 "skipped_deleted" : len (r .SkippedDeleted ),
522+ "layout" : r .Layout ,
523+ }
524+ if r .OfficialUnknown {
525+ summary ["official_unknown" ] = true
526+ } else {
527+ summary ["official" ] = len (r .Official )
455528 }
456529 if len (r .Failed ) > 0 {
457530 summary ["failed" ] = r .Failed
@@ -468,10 +541,13 @@ func emitSkillsTextHints(io *cmdutil.IOStreams, r *skillscheck.SyncResult) {
468541 fmt .Fprintf (io .ErrOut , " Failed skills: %s\n " , strings .Join (r .Failed , ", " ))
469542 }
470543 fmt .Fprintf (io .ErrOut , " To retry all official skills: lark-cli update --force\n " )
544+ case r .Warning != "" :
545+ fmt .Fprintf (io .ErrOut , "%s Skills updated using %s layout\n " , symOK (), r .Layout )
546+ fmt .Fprintf (io .ErrOut , "%s %s\n " , symWarn (), r .Warning )
471547 case r .Force :
472- fmt .Fprintf (io .ErrOut , "%s Skills updated: restored all %d official skills\n " , symOK (), len (r .Official ))
548+ fmt .Fprintf (io .ErrOut , "%s Skills updated using %s layout : restored all %d official skills\n " , symOK (), r . Layout , len (r .Official ))
473549 default :
474- fmt .Fprintf (io .ErrOut , "%s Skills updated: %d official, %d updated, %d added, %d skipped because deleted locally\n " , symOK (), len (r .Official ), len (r .Updated ), len (r .Added ), len (r .SkippedDeleted ))
550+ fmt .Fprintf (io .ErrOut , "%s Skills updated using %s layout : %d official, %d updated, %d added, %d skipped because deleted locally\n " , symOK (), r . Layout , len (r .Official ), len (r .Updated ), len (r .Added ), len (r .SkippedDeleted ))
475551 if len (r .SkippedDeleted ) > 0 {
476552 fmt .Fprintf (io .ErrOut , " To restore all official skills: lark-cli update --force\n " )
477553 }
0 commit comments