@@ -60,17 +60,7 @@ export async function cmdSync(opts: GlobalOpts, options: SyncOptions, inputAllow
6060 } else {
6161 spinner . stop ( )
6262 }
63- let skills = scan . skills
64-
65- skills = await maybeSelectLocalSkills ( skills , {
66- allowPrompt,
67- all : Boolean ( options . all ) ,
68- } )
69- if ( skills . length === 0 ) {
70- outro ( 'Nothing selected.' )
71- return
72- }
73-
63+ const skills = scan . skills
7464 const candidatesSpinner = createSpinner ( 'Checking registry sync state' )
7565 const candidates : Candidate [ ] = [ ]
7666 let supportsResolve : boolean | null = null
@@ -147,23 +137,26 @@ export async function cmdSync(opts: GlobalOpts, options: SyncOptions, inputAllow
147137 }
148138
149139 const synced = candidates . filter ( ( candidate ) => candidate . status === 'synced' )
150- if ( synced . length > 0 ) {
151- const lines = synced
152- . map ( ( candidate ) => `${ candidate . slug } synced (${ candidate . matchVersion ?? 'unknown' } )` )
153- . join ( '\n' )
154- note ( 'Already synced' , lines )
155- }
156-
157140 const actionable = candidates . filter ( ( candidate ) => candidate . status !== 'synced' )
141+ const bump = options . bump ?? 'patch'
142+
158143 if ( actionable . length === 0 ) {
159- outro ( 'Everything is already synced.' )
144+ if ( synced . length > 0 ) {
145+ note ( 'Already synced' , formatCommaList ( synced . map ( formatSyncedSummary ) , 16 ) )
146+ }
147+ outro ( 'Nothing to sync.' )
160148 return
161149 }
162150
151+ note ( 'To sync' , formatBulletList ( actionable . map ( ( candidate ) => formatActionableLine ( candidate , bump ) ) ) )
152+ if ( synced . length > 0 ) {
153+ note ( 'Already synced' , formatBulletList ( synced . map ( formatSyncedLine ) ) )
154+ }
155+
163156 const selected = await selectToUpload ( actionable , {
164157 allowPrompt,
165158 all : Boolean ( options . all ) ,
166- bump : options . bump ?? 'patch' ,
159+ bump,
167160 } )
168161 if ( selected . length === 0 ) {
169162 outro ( 'Nothing selected.' )
@@ -175,7 +168,6 @@ export async function cmdSync(opts: GlobalOpts, options: SyncOptions, inputAllow
175168 return
176169 }
177170
178- const bump = options . bump ?? 'patch'
179171 const tags = options . tags ?? 'latest'
180172
181173 for ( const skill of selected ) {
@@ -216,34 +208,6 @@ async function scanRoots(roots: string[]) {
216208 return { skills : Array . from ( byFolder . values ( ) ) , rootsWithSkills }
217209}
218210
219- async function maybeSelectLocalSkills (
220- skills : SkillFolder [ ] ,
221- params : { allowPrompt : boolean ; all : boolean } ,
222- ) : Promise < SkillFolder [ ] > {
223- if ( params . all || ! params . allowPrompt ) return skills
224- if ( skills . length <= 30 ) return skills
225-
226- const valueByKey = new Map < string , SkillFolder > ( )
227- const choices = skills . map ( ( skill ) => {
228- const key = skill . folder
229- valueByKey . set ( key , skill )
230- return {
231- value : key ,
232- label : skill . slug ,
233- hint : abbreviatePath ( skill . folder ) ,
234- }
235- } )
236-
237- const picked = await multiselect ( {
238- message : `Found ${ skills . length } local skills — select what to sync` ,
239- options : choices ,
240- initialValues : [ ] ,
241- required : false ,
242- } )
243- if ( isCancel ( picked ) ) fail ( 'Canceled' )
244- return picked . map ( ( key ) => valueByKey . get ( String ( key ) ) ) . filter ( Boolean ) as SkillFolder [ ]
245- }
246-
247211async function selectToUpload (
248212 candidates : Candidate [ ] ,
249213 params : { allowPrompt : boolean ; all : boolean ; bump : 'patch' | 'minor' | 'major' } ,
@@ -254,21 +218,17 @@ async function selectToUpload(
254218 const choices = candidates . map ( ( candidate ) => {
255219 const key = candidate . folder
256220 valueByKey . set ( key , candidate )
257- const latest = candidate . latestVersion
258- const next = latest ? semver . inc ( latest , params . bump ) : null
259- const status =
260- candidate . status === 'new' ? 'NEW' : latest && next ? `UPDATE ${ latest } → ${ next } ` : 'UPDATE'
261221 return {
262222 value : key ,
263- label : `${ candidate . slug } ${ status } ` ,
264- hint : candidate . folder ,
223+ label : `${ candidate . slug } ${ formatActionableStatus ( candidate , params . bump ) } ` ,
224+ hint : ` ${ abbreviatePath ( candidate . folder ) } | ${ candidate . fileCount } files` ,
265225 }
266226 } )
267227
268228 const picked = await multiselect ( {
269229 message : 'Select skills to upload' ,
270230 options : choices ,
271- initialValues : candidates . length <= 10 ? choices . map ( ( choice ) => choice . value ) : [ ] ,
231+ initialValues : choices . map ( ( choice ) => choice . value ) ,
272232 required : false ,
273233 } )
274234 if ( isCancel ( picked ) ) fail ( 'Canceled' )
@@ -330,3 +290,40 @@ function abbreviatePath(value: string) {
330290 if ( value . startsWith ( home ) ) return `~${ value . slice ( home . length ) } `
331291 return value
332292}
293+
294+ function formatActionableStatus (
295+ candidate : Candidate ,
296+ bump : 'patch' | 'minor' | 'major' ,
297+ ) : string {
298+ if ( candidate . status === 'new' ) return 'NEW'
299+ const latest = candidate . latestVersion
300+ const next = latest ? semver . inc ( latest , bump ) : null
301+ if ( latest && next ) return `UPDATE ${ latest } → ${ next } `
302+ return 'UPDATE'
303+ }
304+
305+ function formatActionableLine ( candidate : Candidate , bump : 'patch' | 'minor' | 'major' ) : string {
306+ return `${ candidate . slug } ${ formatActionableStatus ( candidate , bump ) } (${ candidate . fileCount } files)`
307+ }
308+
309+ function formatSyncedLine ( candidate : Candidate ) : string {
310+ const version = candidate . matchVersion ?? candidate . latestVersion ?? 'unknown'
311+ return `${ candidate . slug } synced (${ version } )`
312+ }
313+
314+ function formatSyncedSummary ( candidate : Candidate ) : string {
315+ const version = candidate . matchVersion ?? candidate . latestVersion
316+ return version ? `${ candidate . slug } @${ version } ` : candidate . slug
317+ }
318+
319+ function formatBulletList ( lines : string [ ] ) : string {
320+ return lines . map ( ( line ) => `- ${ line } ` ) . join ( '\n' )
321+ }
322+
323+ function formatCommaList ( values : string [ ] , max : number ) {
324+ if ( values . length === 0 ) return ''
325+ if ( values . length <= max ) return values . join ( ', ' )
326+ const head = values . slice ( 0 , Math . max ( 1 , max - 1 ) )
327+ const rest = values . length - head . length
328+ return `${ head . join ( ', ' ) } , ... +${ rest } more`
329+ }
0 commit comments