()
| 755 | // ── CLI ───────────────────────────────────────────────────────────────────── |
| 756 | |
| 757 | export function buildCli() { |
| 758 | // One-time migration of ideas data from ~/.ft-bookmarks/automation/{ideas,adjacent}/ |
| 759 | // to ~/.fieldtheory/ideas/. Idempotent and cheap on hot paths — two fs.existsSync |
| 760 | // calls after the first run. Runs before any command so every subcommand sees |
| 761 | // the new layout. |
| 762 | try { |
| 763 | const migration = migrateLegacyIdeasData(); |
| 764 | if (migration.migrated) { |
| 765 | process.stderr.write(` Migrated ideas data → ${migration.newRoot}\n`); |
| 766 | process.stderr.write(` Legacy copies left intact at ${migration.legacyIdeasRoot} and ${migration.legacyAdjacentRoot}.\n`); |
| 767 | } |
| 768 | } catch (err) { |
| 769 | process.stderr.write(` Warning: ideas data migration failed — ${(err as Error).message}\n`); |
| 770 | } |
| 771 | |
| 772 | const program = new Command(); |
| 773 | |
| 774 | async function rebuildIndex(): Promise<number> { |
| 775 | process.stderr.write(' Building search index...\n'); |
| 776 | const idx = await buildIndex(); |
| 777 | process.stderr.write(` \u2713 ${idx.recordCount} bookmarks indexed (${idx.newRecords} new)\n`); |
| 778 | return idx.newRecords; |
| 779 | } |
| 780 | |
| 781 | async function classifyNew(override?: string): Promise<void> { |
| 782 | const engine = await resolveEngine({ override }); |
| 783 | |
| 784 | const start = Date.now(); |
| 785 | process.stderr.write(' Classifying new bookmarks (categories)...\n'); |
| 786 | const catResult = await classifyWithLlm({ |
| 787 | engine, |
| 788 | onBatch: (done: number, total: number) => { |
| 789 | const pct = total > 0 ? Math.round((done / total) * 100) : 0; |
| 790 | const elapsed = Math.round((Date.now() - start) / 1000); |
| 791 | process.stderr.write(` Categories: ${done}/${total} (${pct}%) \u2502 ${elapsed}s elapsed\n`); |
| 792 | }, |
| 793 | }); |
| 794 | if (catResult.classified > 0) { |
| 795 | process.stderr.write(` \u2713 ${catResult.classified} categorized\n`); |
| 796 | } |
| 797 | |
| 798 | const domStart = Date.now(); |
| 799 | process.stderr.write(' Classifying new bookmarks (domains)...\n'); |
| 800 | const domResult = await classifyDomainsWithLlm({ |
| 801 | engine, |
| 802 | all: false, |
| 803 | onBatch: (done: number, total: number) => { |
| 804 | const pct = total > 0 ? Math.round((done / total) * 100) : 0; |
| 805 | const elapsed = Math.round((Date.now() - domStart) / 1000); |
| 806 | process.stderr.write(` Domains: ${done}/${total} (${pct}%) \u2502 ${elapsed}s elapsed\n`); |
| 807 | }, |
| 808 | }); |
| 809 | if (domResult.classified > 0) { |
| 810 | process.stderr.write(` \u2713 ${domResult.classified} domains assigned\n`); |
| 811 | } |
| 812 | } |
| 813 | |
| 814 | program |
no test coverage detected