| 274 | } |
| 275 | |
| 276 | func SyncSkills(opts SyncOptions) *SyncResult { |
| 277 | if opts.Now == nil { |
| 278 | opts.Now = time.Now |
| 279 | } |
| 280 | if opts.Runner == nil { |
| 281 | return &SyncResult{Action: "failed", Err: fmt.Errorf("skills runner is nil")} |
| 282 | } |
| 283 | |
| 284 | // --- Step 1: List official skills --- |
| 285 | official, reason, ok := listOfficialSkills(opts.Runner) |
| 286 | if !ok { |
| 287 | return fallbackFullInstall(opts, reason, nil) |
| 288 | } |
| 289 | |
| 290 | // --- Step 2: List local (installed) skills --- |
| 291 | local, ok := listLocalSkills(opts.Runner) |
| 292 | if !ok { |
| 293 | return fallbackFullInstall(opts, "local skills list failed or parsed as empty", official) |
| 294 | } |
| 295 | |
| 296 | // --- Step 3: Read previous state --- |
| 297 | previous, readable, err := ReadState() |
| 298 | if err != nil { |
| 299 | readable = false |
| 300 | previous = nil |
| 301 | } |
| 302 | |
| 303 | plan := PlanSync(SyncInput{ |
| 304 | Version: opts.Version, |
| 305 | OfficialSkills: official, |
| 306 | LocalSkills: local, |
| 307 | PreviousState: previous, |
| 308 | StateReadable: readable, |
| 309 | Force: opts.Force, |
| 310 | }) |
| 311 | |
| 312 | result := &SyncResult{ |
| 313 | Action: "synced", |
| 314 | Official: plan.OfficialSkills, |
| 315 | Updated: plan.ToUpdate, |
| 316 | Added: plan.Added, |
| 317 | SkippedDeleted: plan.SkippedDeleted, |
| 318 | Force: opts.Force, |
| 319 | } |
| 320 | |
| 321 | if len(plan.ToUpdate) == 0 { |
| 322 | return fallbackFullInstall(opts, "toUpdate skills empty fallback", official) |
| 323 | } |
| 324 | |
| 325 | if len(plan.ToUpdate) > 0 { |
| 326 | installResult := opts.Runner.InstallSkill(plan.ToUpdate) |
| 327 | if installResult == nil || installResult.Err != nil { |
| 328 | return fallbackFullInstall(opts, resultDetail(installResult), official) |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | state := SkillsState{ |
| 333 | Version: opts.Version, |