Skip to content

Commit 91233df

Browse files
committed
fix: improve sync selection output
1 parent 2a2749f commit 91233df

File tree

1 file changed

+53
-56
lines changed
  • packages/clawdhub/src/cli/commands

1 file changed

+53
-56
lines changed

packages/clawdhub/src/cli/commands/sync.ts

Lines changed: 53 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
247211
async 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

Comments
 (0)