|
1 | 1 | import { realpath } from 'node:fs/promises' |
2 | 2 | import { homedir } from 'node:os' |
3 | 3 | import { resolve } from 'node:path' |
4 | | -import { intro, isCancel, multiselect, note, outro, text } from '@clack/prompts' |
| 4 | +import { intro, isCancel, multiselect, outro, text } from '@clack/prompts' |
5 | 5 | import semver from 'semver' |
6 | 6 | import { readGlobalConfig } from '../../config.js' |
7 | 7 | import { apiRequest, downloadZip } from '../../http.js' |
@@ -61,17 +61,17 @@ export async function cmdSync(opts: GlobalOpts, options: SyncOptions, inputAllow |
61 | 61 | spinner.stop() |
62 | 62 | if (scan.skills.length === 0) |
63 | 63 | fail('No skills found (checked workdir and known Clawdis/Clawd locations)') |
64 | | - note( |
| 64 | + printSection( |
65 | 65 | `No skills in workdir. Found ${scan.skills.length} in fallback locations.`, |
66 | | - wrapNoteBody(formatList(scan.rootsWithSkills, 10)), |
| 66 | + formatList(scan.rootsWithSkills, 10), |
67 | 67 | ) |
68 | 68 | } else { |
69 | 69 | spinner.stop() |
70 | 70 | } |
71 | 71 | const deduped = dedupeSkillsBySlug(scan.skills) |
72 | 72 | const skills = deduped.skills |
73 | 73 | if (deduped.duplicates.length > 0) { |
74 | | - note('Skipped duplicate slugs', wrapNoteBody(formatCommaList(deduped.duplicates, 16))) |
| 74 | + printSection('Skipped duplicate slugs', formatCommaList(deduped.duplicates, 16)) |
75 | 75 | } |
76 | 76 | const parsingSpinner = createSpinner('Parsing local skills') |
77 | 77 | const locals: LocalSkill[] = [] |
@@ -123,23 +123,21 @@ export async function cmdSync(opts: GlobalOpts, options: SyncOptions, inputAllow |
123 | 123 |
|
124 | 124 | if (actionable.length === 0) { |
125 | 125 | if (synced.length > 0) { |
126 | | - note('Already synced', wrapNoteBody(formatCommaList(synced.map(formatSyncedSummary), 16))) |
| 126 | + printSection('Already synced', formatCommaList(synced.map(formatSyncedSummary), 16)) |
127 | 127 | } |
128 | 128 | outro('Nothing to sync.') |
129 | 129 | return |
130 | 130 | } |
131 | 131 |
|
132 | | - note( |
| 132 | + printSection( |
133 | 133 | 'To sync', |
134 | | - wrapNoteBody( |
135 | | - formatBulletList( |
136 | | - actionable.map((candidate) => formatActionableLine(candidate, bump)), |
137 | | - 20, |
138 | | - ), |
| 134 | + formatBulletList( |
| 135 | + actionable.map((candidate) => formatActionableLine(candidate, bump)), |
| 136 | + 20, |
139 | 137 | ), |
140 | 138 | ) |
141 | 139 | if (synced.length > 0) { |
142 | | - note('Already synced', wrapNoteBody(formatSyncedDisplay(synced))) |
| 140 | + printSection('Already synced', formatSyncedDisplay(synced)) |
143 | 141 | } |
144 | 142 |
|
145 | 143 | const selected = await selectToUpload(actionable, { |
@@ -392,6 +390,19 @@ function formatList(values: string[], max: number) { |
392 | 390 | return [...head, `… +${rest} more`].join('\n') |
393 | 391 | } |
394 | 392 |
|
| 393 | +function printSection(title: string, body?: string) { |
| 394 | + const trimmed = body?.trim() |
| 395 | + if (!trimmed) { |
| 396 | + console.log(title) |
| 397 | + return |
| 398 | + } |
| 399 | + if (trimmed.includes('\n')) { |
| 400 | + console.log(`\n${title}\n${trimmed}`) |
| 401 | + return |
| 402 | + } |
| 403 | + console.log(`${title}: ${trimmed}`) |
| 404 | +} |
| 405 | + |
395 | 406 | function abbreviatePath(value: string) { |
396 | 407 | const home = homedir() |
397 | 408 | if (value.startsWith(home)) return `~${value.slice(home.length)}` |
@@ -449,58 +460,6 @@ function formatSyncedDisplay(synced: Candidate[]) { |
449 | 460 | return formatCommaList(synced.map(formatSyncedSummary), 24) |
450 | 461 | } |
451 | 462 |
|
452 | | -function wrapNoteBody(text: string) { |
453 | | - const width = noteWrapWidth() |
454 | | - return text |
455 | | - .split('\n') |
456 | | - .map((line) => wrapLine(line, width)) |
457 | | - .join('\n') |
458 | | -} |
459 | | - |
460 | | -function noteWrapWidth() { |
461 | | - const columns = process.stdout.columns ?? 80 |
462 | | - return Math.min(80, Math.max(20, columns - 4)) |
463 | | -} |
464 | | - |
465 | | -function wrapLine(line: string, width: number) { |
466 | | - if (line.length <= width) return line |
467 | | - if (line.startsWith('- ')) { |
468 | | - return wrapWords(line.slice(2), width - 2, '- ', ' ') |
469 | | - } |
470 | | - return wrapWords(line, width, '', '') |
471 | | -} |
472 | | - |
473 | | -function wrapWords(text: string, width: number, firstPrefix: string, nextPrefix: string) { |
474 | | - const words = text.trim().split(/\s+/).filter(Boolean) |
475 | | - if (words.length === 0) return firstPrefix.trimEnd() |
476 | | - const lines: string[] = [] |
477 | | - let current = '' |
478 | | - for (const word of words) { |
479 | | - if (word.length > width) { |
480 | | - if (current) { |
481 | | - lines.push(current) |
482 | | - current = '' |
483 | | - } |
484 | | - for (let i = 0; i < word.length; i += width) { |
485 | | - lines.push(word.slice(i, i + width)) |
486 | | - } |
487 | | - continue |
488 | | - } |
489 | | - if (!current) { |
490 | | - current = word |
491 | | - continue |
492 | | - } |
493 | | - if (current.length + 1 + word.length <= width) { |
494 | | - current = `${current} ${word}` |
495 | | - continue |
496 | | - } |
497 | | - lines.push(current) |
498 | | - current = word |
499 | | - } |
500 | | - if (current) lines.push(current) |
501 | | - return lines.map((line, index) => `${index === 0 ? firstPrefix : nextPrefix}${line}`).join('\n') |
502 | | -} |
503 | | - |
504 | 463 | function formatCommaList(values: string[], max: number) { |
505 | 464 | if (values.length === 0) return '' |
506 | 465 | if (values.length <= max) return values.join(', ') |
|
0 commit comments