import '@johnlindquist/kit'
interface ClipboardEntry {
id: string
content: string
type: 'text' | 'image' | 'url'
category: string
timestamp: string
preview: string
}
interface GroupedEntries {
[category: string]: ClipboardEntry[]
}
const MAX_ENTRIES = 100
const MIN_CONTENT_LENGTH = 5
const PREVIEW_LENGTH = 100
const CATEGORY_SAMPLE_LENGTH = 200
const ENTRIES_PER_CATEGORY = 10
const archiveDb = await db('clipboard-archive', { entries: [] as ClipboardEntry[] })
const categorizeContent = ai('Categorize this content into one of these categories: work, personal, code, reference, media, other. Return only the category name.')
function getContentType(content: string): ClipboardEntry['type'] {
if (content.startsWith('http://') || content.startsWith('https://')) {
return 'url'
}
return 'text'
}
function createPreview(content: string): string {
return content.length > PREVIEW_LENGTH
? content.substring(0, PREVIEW_LENGTH) + '...'
: content
}
async function archiveClipboardContent(): Promise<void> {
try {
const currentClipboard = await clipboard.readText()
if (!currentClipboard || currentClipboard.length <= MIN_CONTENT_LENGTH) {
return
}
const existingEntries = archiveDb.entries as ClipboardEntry[]
const isDuplicate = existingEntries.some(entry => entry.content === currentClipboard)
if (isDuplicate) {
return
}
let category = 'other'
try {
const categoryResult = await categorizeContent(
currentClipboard.substring(0, CATEGORY_SAMPLE_LENGTH)
)
category = categoryResult.toLowerCase().trim()
} catch (error) {
console.warn('Failed to categorize content, using default category:', error)
}
const newEntry: ClipboardEntry = {
id: uuid(),
content: currentClipboard,
type: getContentType(currentClipboard),
category,
timestamp: new Date().toISOString(),
preview: createPreview(currentClipboard)
}
archiveDb.entries.unshift(newEntry)
if (archiveDb.entries.length > MAX_ENTRIES) {
archiveDb.entries = archiveDb.entries.slice(0, MAX_ENTRIES)
}
await archiveDb.write()
} catch (error) {
console.error('Failed to archive clipboard content:', error)
}
}
function groupEntriesByCategory(entries: ClipboardEntry[]): GroupedEntries {
return entries.reduce((groups: GroupedEntries, entry) => {
const category = entry.category || 'other'
if (!groups[category]) {
groups[category] = []
}
groups[category].push(entry)
return groups
}, {})
}
function createChoicesFromGroups(groupedEntries: GroupedEntries) {
const choices = []
for (const [category, entries] of Object.entries(groupedEntries)) {
choices.push({
name: `📁 ${category.toUpperCase()}`,
group: category,
info: true,
miss: true
})
entries.slice(0, ENTRIES_PER_CATEGORY).forEach(entry => {
choices.push({
name: entry.preview,
description: `${formatDateToNow(new Date(entry.timestamp))} • ${entry.type}`,
value: entry,
group: category,
preview: () => md(`
### ${entry.type === 'url' ? 'URL' : 'Text'} Content
**Category:** ${entry.category}
**Saved:** ${formatDate(new Date(entry.timestamp), 'PPpp')}
\`\`\`
${entry.content}
\`\`\`
`)
})
})
}
return choices
}
async function deleteEntry(entryId: string): Promise<void> {
try {
archiveDb.entries = archiveDb.entries.filter(e => e.id !== entryId)
await archiveDb.write()
await toast('Entry deleted')
await run('smart-clipboard-archive')
} catch (error) {
console.error('Failed to delete entry:', error)
await toast('Failed to delete entry')
}
}
async function main(): Promise<void> {
await archiveClipboardContent()
const existingEntries = archiveDb.entries as ClipboardEntry[]
if (existingEntries.length === 0) {
await div(md('# Clipboard Archive Empty\nCopy some text to start building your archive!'))
return
}
const groupedEntries = groupEntriesByCategory(existingEntries)
const choices = createChoicesFromGroups(groupedEntries)
const selectedEntry = await arg({
placeholder: 'Search your clipboard archive...',
enter: 'Paste Selected',
actions: [
{
name: 'Copy to Clipboard',
shortcut: 'cmd+c',
onAction: async (input, { focused }) => {
if (focused?.value?.content) {
await clipboard.writeText(focused.value.content)
await toast('Copied to clipboard!')
}
}
},
{
name: 'Paste & Replace',
shortcut: 'cmd+v',
onAction: async (input, { focused }) => {
if (focused?.value?.content) {
await setSelectedText(focused.value.content)
submit(focused.value)
}
}
},
{
name: 'Delete Entry',
shortcut: 'cmd+d',
onAction: async (input, { focused }) => {
if (focused?.value?.id) {
await deleteEntry(focused.value.id)
}
}
},
{
name: 'Open URL',
shortcut: 'cmd+o',
condition: (choice) => choice?.value?.type === 'url',
onAction: async (input, { focused }) => {
if (focused?.value?.type === 'url') {
try {
await browse(focused.value.content)
} catch (error) {
console.error('Failed to open URL:', error)
await toast('Failed to open URL')
}
}
}
}
]
}, choices)
if (selectedEntry?.content) {
await setSelectedText(selectedEntry.content)
}
}
try {
await main()
} catch (error) {
console.error('Script execution failed:', error)
await div(md('# Error\nSomething went wrong. Please try again.'))
}