codeply-cli 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/THIRD_PARTY_NOTICES.md +63 -0
  2. package/bin/codeply.js +562 -370
  3. package/lib/agent.mjs +1627 -160
  4. package/lib/ai.js +1107 -387
  5. package/lib/apply-limit.js +33 -51
  6. package/lib/apply-patch.js +217 -0
  7. package/lib/arity-table.json +138 -0
  8. package/lib/arity.js +70 -0
  9. package/lib/auth.js +118 -112
  10. package/lib/commands.js +103 -0
  11. package/lib/config.js +392 -136
  12. package/lib/design-library/artifact-template.html +500 -0
  13. package/lib/design-library/assemble-artifact.mjs +21 -0
  14. package/lib/design-library/build.mjs +226 -0
  15. package/lib/design-library/cleanup.mjs +45 -0
  16. package/lib/design-library/index.json +22380 -0
  17. package/lib/design-library/make-table-data.mjs +39 -0
  18. package/lib/design-library/query.mjs +104 -0
  19. package/lib/diagnostics.js +192 -0
  20. package/lib/edit-engine.js +171 -14
  21. package/lib/github-agent.mjs +290 -0
  22. package/lib/history.js +75 -0
  23. package/lib/mcp.js +258 -0
  24. package/lib/native-tools.mjs +189 -0
  25. package/lib/oauth-connectors.js +478 -0
  26. package/lib/permissions.js +109 -0
  27. package/lib/plugins.js +353 -0
  28. package/lib/render-md.mjs +617 -617
  29. package/lib/server.mjs +457 -0
  30. package/lib/session-db.js +220 -0
  31. package/lib/share.js +168 -0
  32. package/lib/skills.js +80 -22
  33. package/lib/snapshot.js +146 -0
  34. package/lib/subagents.js +164 -0
  35. package/lib/terminal-hints.js +154 -0
  36. package/lib/textarea.mjs +258 -258
  37. package/lib/tools.mjs +1658 -79
  38. package/lib/tui.js +7 -7
  39. package/lib/tui.mjs +1086 -992
  40. package/lib/typecheck-worker.js +147 -0
  41. package/lib/web-tools.js +158 -0
  42. package/package.json +4 -3
  43. package/skills/premium-web-design/SKILL.md +154 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Builds a private, internal reference library of real app screenshots for
3
+ * design work. Sourced entirely from Apple's public iTunes Search API
4
+ * (https://itunes.apple.com/search) - official, free, unauthenticated, and
5
+ * returns screenshotUrls apps publish themselves for their own App Store
6
+ * listing. Not scraped from inside running apps (that's the gray area that
7
+ * got the unofficial Mobbin client killed). Output is never republished
8
+ * publicly - it's a local index used only to inform design work in this
9
+ * project.
10
+ */
11
+ import { writeFile, mkdir } from 'node:fs/promises';
12
+ import path from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const OUT_PATH = path.join(__dirname, 'index.json');
17
+
18
+ const CATEGORIES = {
19
+ productivity: [
20
+ 'Notion', 'Todoist', 'Things 3', 'TickTick', 'Asana', 'Trello', 'ClickUp', 'Linear', 'Evernote', 'Microsoft To Do',
21
+ 'Google Keep', 'Any.do', 'OmniFocus', 'Bear', 'Notability', 'GoodNotes', 'Craft Docs', 'Obsidian', 'Milanote', 'Miro',
22
+ 'Basecamp', 'Monday.com', 'Airtable', 'Coda', 'Zapier', 'IFTTT', 'Toggl Track', 'RescueTime', 'Forest', 'Habitica',
23
+ 'Streaks', 'Fantastical', 'Timepage', 'Google Calendar', 'Microsoft Outlook', 'Spark Mail', 'Superhuman', 'Simplenote', 'Day One', 'Journey',
24
+ 'Pocket', 'Instapaper', 'Readwise', 'Raindrop.io', '1Password', 'LastPass', 'Dashlane', 'Bitwarden', 'Adobe Scan', 'CamScanner',
25
+ 'PDF Expert', 'Documents by Readdle', 'WPS Office', 'Microsoft Word', 'Microsoft Excel', 'Microsoft PowerPoint', 'Google Docs', 'Google Sheets', 'Google Drive', 'Dropbox',
26
+ 'Box', 'Microsoft OneDrive', 'Zoom', 'Google Meet', 'Microsoft Teams', 'Cisco Webex Meetings', 'Loom', 'Calendly', 'TeamViewer', 'AnyDesk',
27
+ 'Sunsama', 'Motion', 'Structured', 'TimeTree', 'Clockify', 'Harvest', 'WorkFlowy', 'Roam Research', 'Logseq', 'Remember The Milk',
28
+ 'Google Tasks', 'Apple Notes', 'Microsoft OneNote', 'Standard Notes', 'Joplin', 'Zoho Notebook', 'Zoho Projects', 'Smartsheet', 'Wrike', 'Height',
29
+ 'Confluence', 'Jira', 'GitHub', 'GitLab', 'Flow', 'Focus To-Do', 'Be Focused', 'Complice', 'Amazing Marvin', 'Sorted3',
30
+ ],
31
+ finance: [
32
+ 'Robinhood', 'Cash App', 'Venmo', 'Mint: Budget & Finance', 'PayPal', 'Revolut', 'Chime', 'Coinbase', 'Wise', 'Credit Karma',
33
+ 'Zelle', 'Fidelity Investments', 'Charles Schwab', 'E*TRADE', 'Webull', 'SoFi', 'Ally Bank', 'Capital One Mobile', 'Chase Mobile', 'Bank of America Mobile Banking',
34
+ 'Wells Fargo Mobile', 'American Express', 'Discover Mobile', 'Acorns', 'Stash', 'Betterment', 'Wealthfront', 'YNAB', 'PocketGuard', 'Monarch Money',
35
+ 'Empower', 'NerdWallet', 'Truebill', 'Rocket Money', 'Klarna', 'Afterpay', 'Affirm', 'Splitwise', 'Zelle', 'Google Pay',
36
+ 'Apple Wallet', 'Samsung Wallet', 'Binance', 'Kraken', 'Gemini', 'Crypto.com', 'Trust Wallet', 'MetaMask', 'Plaid', 'Varo Bank',
37
+ 'Current', 'MoneyLion', 'Dave', 'Brigit', 'Empower Finance', 'Simplifi by Quicken', 'Quicken', 'QuickBooks', 'FreshBooks', 'Xero',
38
+ 'Wave', 'Square Point of Sale', 'Stripe Dashboard', 'Toast', 'Payoneer', 'TransferWise', 'Remitly', 'WorldRemit', 'Western Union', 'OFX',
39
+ 'TD Bank', 'US Bank', 'PNC Mobile', 'Citi Mobile', 'Navy Federal Credit Union', 'USAA Mobile', 'Marcus by Goldman Sachs', 'Public.com', 'M1 Finance', 'Vanguard',
40
+ 'TD Ameritrade Mobile', 'Interactive Brokers', 'TradingView', 'Yahoo Finance', 'Bloomberg', 'CNBC', 'Personal Capital', 'Tally', 'Digit', 'Qapital',
41
+ 'Prism', 'Albert', 'Even', 'Level Money', 'Honeydue', 'Zeta', 'Copilot Money', 'Lunch Money', 'Wallet by BudgetBakers', 'Spendee',
42
+ ],
43
+ shopping: [
44
+ 'Amazon Shopping', 'Etsy', 'eBay', 'SHEIN', 'ASOS', 'Nike', 'Instacart', 'Target', 'Wish', 'Zappos',
45
+ 'Walmart', 'Best Buy', 'Home Depot', "Lowe's", 'Costco', 'Sephora', 'Ulta Beauty', 'H&M', 'Zara', 'Uniqlo',
46
+ 'Adidas', 'Puma', 'Foot Locker', 'Vinted', 'Depop', 'Poshmark', 'ThredUp', 'Mercari', 'OfferUp', 'Facebook Marketplace',
47
+ 'Temu', 'AliExpress', 'Alibaba', 'Wayfair', 'IKEA', 'Overstock', 'Chewy', 'Petco', 'Kroger', 'Whole Foods Market',
48
+ 'Safeway', 'Shipt', 'Gopuff', 'Shopify', 'StockX', 'GOAT', 'Grailed', 'Rakuten', 'Honey', 'Capital One Shopping',
49
+ 'Groupon', 'RetailMeNot', 'Flipp', 'Ibotta', 'Fetch Rewards', 'Shopkick', 'Michaels', "Kohl's", 'Macy\'s', 'Nordstrom',
50
+ 'JCPenney', "Victoria's Secret", 'Gap', 'Old Navy', 'Banana Republic', 'American Eagle', 'Urban Outfitters', 'Forever 21', 'Lululemon', 'Under Armour',
51
+ 'Sephora Collection', 'Bath & Body Works', 'CVS Pharmacy', 'Walgreens', 'Rite Aid', 'GameStop', 'PlayStation App', 'Xbox', 'Steam', 'Newegg',
52
+ 'B&H Photo', 'REI', "Dick's Sporting Goods", 'Academy Sports', 'Sam\'s Club', 'BJ\'s Wholesale', 'Aldi', 'Trader Joe\'s', 'Fresh Direct', 'Boxed',
53
+ 'Zulily', 'Wildberries', 'Farfetch', 'SSENSE', 'Net-a-Porter', 'The RealReal', 'Carousell', 'OLX', 'Letgo', 'Craigslist',
54
+ ],
55
+ social: [
56
+ 'Instagram', 'WhatsApp Messenger', 'Telegram Messenger', 'Discord', 'Slack', 'X', 'Snapchat', 'TikTok', 'Messenger', 'LinkedIn',
57
+ 'Facebook', 'Pinterest', 'Reddit', 'Tumblr', 'BeReal', 'Threads', 'Mastodon', 'Bluesky', 'Clubhouse', 'Nextdoor',
58
+ 'Signal', 'Viber', 'WeChat', 'Line', 'KakaoTalk', 'Skype', 'Google Chat', 'Twitch', 'YouTube', 'YouTube Kids',
59
+ 'Quora', 'Yelp', 'Meetup', 'Bumble', 'Tinder', 'Hinge', 'OkCupid', 'Match', 'Grindr', 'Her',
60
+ 'Coffee Meets Bagel', 'Plenty of Fish', 'Badoo', 'Wizz', 'Yubo', 'Marco Polo', 'GroupMe', 'Band', 'Amino', 'Fanbase',
61
+ 'Poparazzi', 'Locket Widget', 'Gas', 'NGL', 'Sendit', 'LMK', 'Yik Yak', 'Fizz', 'Geneva', 'Circle',
62
+ 'Litmatch', 'Tagged', 'MeetMe', 'Skout', 'Hily', 'Feeld', 'Lex', 'Chappy', 'Taimi', 'Zoosk',
63
+ 'eharmony', 'Christian Mingle', 'JDate', 'Raya', 'Wingman', 'Once', 'Happn', 'Bristlr', 'Down Dating', 'Pure',
64
+ 'Wapa', 'Scruff', 'Jack\'d', 'Growlr', 'Surge', 'Chispa', 'BLK', 'CoffeeMeetsBagel', 'FriendsDate', 'Meetup Local',
65
+ 'Fetlife', 'Alt.com', 'Grouper', 'Timeleft', 'Cocktail Party', 'Peanut', 'Bumble BFF', 'Meetup+', 'Discord Nitro', 'Steam Chat',
66
+ ],
67
+ travel: [
68
+ 'Airbnb', 'Booking.com', 'Uber', 'Lyft', 'Google Maps', 'Expedia', 'Skyscanner', 'Hopper', 'Tripadvisor', 'Waze',
69
+ 'Kayak', 'Trivago', 'Hotels.com', 'Vrbo', 'Priceline', 'Southwest Airlines', 'Delta', 'United Airlines', 'American Airlines', 'JetBlue',
70
+ 'Ryanair', 'easyJet', 'British Airways', 'Emirates', 'Qatar Airways', 'Turkish Airlines', 'Lufthansa', 'Air France', 'Marriott Bonvoy', 'Hilton Honors',
71
+ 'IHG Hotels & Resorts', 'World of Hyatt', 'Best Western', 'Choice Hotels', 'Google Flights', 'Rome2rio', 'Omio', 'FlixBus', 'Greyhound', 'Amtrak',
72
+ 'Trainline', 'TripIt', 'CityMapper', 'Transit', 'Moovit', 'Apple Maps', 'HERE WeGo', 'Maps.me', 'GasBuddy', 'Parkopedia',
73
+ 'SpotHero', 'Turo', 'Zipcar', 'Getaround', 'Enterprise Rent-A-Car', 'Hertz', 'Avis', 'National Car Rental', 'Sixt', 'Rentalcars.com',
74
+ 'Grab', 'Gojek', 'Ola', 'Bolt', 'DiDi', 'FreeNow', 'Careem', 'Curb', 'BlaBlaCar', 'Couchsurfing',
75
+ 'HostelWorld', 'Agoda', 'Travelocity', 'Orbitz', 'CheapOair', 'Google Trips', 'Roadtrippers', 'The Vacationer', 'PackPoint', 'XE Currency',
76
+ 'Duolingo', 'Google Translate', 'iTranslate', 'TripCase', 'FlightAware', 'FlightRadar24', 'MyRadar', 'Windy', 'AccuWeather', 'The Weather Channel',
77
+ 'National Parks', 'AllTrails', 'Komoot', 'Gaia GPS', 'Wikiloc', 'Yelp', 'OpenTable', 'Resy', 'The Fork', 'Time Out',
78
+ ],
79
+ food_delivery: [
80
+ 'DoorDash', 'Uber Eats', 'Grubhub', 'Deliveroo', 'OpenTable', 'Starbucks', "McDonald's", 'Postmates', "Domino's", 'Chick-fil-A',
81
+ "Wendy's", 'Burger King', 'Taco Bell', 'Panera Bread', 'Chipotle', 'Subway', "Papa John's", 'Pizza Hut', "Dunkin'", 'Panda Express',
82
+ 'Shake Shack', "Culver's", 'In-N-Out', 'Five Guys', 'Sonic Drive-In', 'Jack in the Box', 'KFC', 'Popeyes', 'Whataburger', "Raising Cane's",
83
+ 'Grubhub Guarantee', 'Seamless', 'Caviar', 'ChowNow', 'Toast Takeout', 'Slice', 'EatStreet', 'Waitr', 'Foodpanda', 'Zomato',
84
+ 'Swiggy', 'Just Eat', 'iFood', 'Rappi', 'Talabat', 'Careem NOW', 'Wolt', 'Glovo', 'Bolt Food', 'Yandex Eats',
85
+ 'Meituan', 'Ele.me', 'Baemin', "Yogiyo", 'GrabFood', 'foodpanda Taiwan', 'Menulog', 'HungryPanda', 'ezCater', 'Cater2.me',
86
+ 'Resy', 'Yelp Reservations', 'The Fork', 'Tock', 'Sevenrooms', 'Reserve', 'Bon Appetour', 'HowUdish', 'Feastly', 'EatWith',
87
+ 'Blue Apron', 'HelloFresh', 'Home Chef', 'Sunbasket', 'EveryPlate', 'Green Chef', 'Factor', 'Freshly', 'Daily Harvest', 'Gobble',
88
+ 'Instacart', 'Shipt', 'Gopuff', 'Amazon Fresh', 'FreshDirect', 'Peapod', 'Boxed', 'Thrive Market', 'Misfits Market', 'Imperfect Foods',
89
+ 'Starbucks Rewards', 'Dutch Bros', 'Tim Hortons', 'Krispy Kreme', "Jimmy John's", 'Firehouse Subs', "Jersey Mike's", 'Qdoba', 'Moe\'s Southwest Grill', 'El Pollo Loco',
90
+ ],
91
+ health_fitness: [
92
+ 'Strava', 'MyFitnessPal', 'Headspace', 'Calm', 'Peloton', 'Fitbit', 'Nike Run Club', 'WHOOP', 'Noom', 'Sleep Cycle',
93
+ 'Apple Fitness+', 'Garmin Connect', 'Samsung Health', 'Google Fit', 'Fitbod', 'Freeletics', 'Nike Training Club', 'Adidas Training', 'JEFIT', 'Strong',
94
+ 'Sworkit', 'Aaptiv', 'Daily Yoga', 'Down Dog', 'Yoga Studio', 'Glo', 'Alo Moves', 'Centr', 'Fiit', 'Ladder',
95
+ 'Future', 'Tonal', 'Zwift', 'TrainingPeaks', 'Wahoo Fitness', 'Runkeeper', 'MapMyRun', 'MapMyFitness', 'Couch to 5K', 'Runna',
96
+ 'Cronometer', 'Lose It!', 'Lifesum', 'Fooducate', 'Yazio', 'Carb Manager', 'Fastic', 'Zero Fasting', 'DoFasting', 'BodyFast',
97
+ 'Sleep Score', 'Pillow', 'AutoSleep', 'SleepWatch', 'Oura', 'WHOOP Journal', 'Rise Sleep', 'Insight Timer', 'Ten Percent Happier', 'Waking Up',
98
+ 'Simple Habit', 'Breathwrk', 'Balance', 'Reflectly', 'Sanvello', 'Talkspace', 'BetterHelp', 'Cerebral', 'Calm Kids', 'Smiling Mind',
99
+ 'WW (WeightWatchers)', 'Weight Watchers', 'Noom Coach', 'Fitbit Coach', 'Sworkit Kids', 'GymRat', 'Fitness Buddy', 'Gymshark Training', 'Alan Fitness', 'Sweat',
100
+ 'ClassPass', 'Mindbody', 'Gympass', 'Barry\'s', 'Orangetheory Fitness', 'F45 Training', 'CrossFit', 'SoulCycle', 'Planet Fitness', 'Life Time',
101
+ '23andMe', 'Clue Period Tracker', 'Flo Period Tracker', 'Ovia', 'Glow', 'Natural Cycles', 'Fitbit Blaze', 'Garmin Vivosmart', 'Withings Health Mate', 'iHealth MyVitals',
102
+ ],
103
+ education: [
104
+ 'Duolingo', 'Khan Academy', 'Coursera', 'Udemy', 'Quizlet', 'Google Classroom', 'Brilliant', 'Skillshare', 'Photomath', 'Babbel',
105
+ 'Rosetta Stone', 'Busuu', 'Memrise', 'HelloTalk', 'Tandem', 'Lingoda', 'Drops', 'Mondly', 'Anki', 'Kahoot!',
106
+ 'Blackboard', 'Canvas Student', 'Schoology', 'Remind', 'ClassDojo', 'Seesaw', 'Edmodo', 'IXL', 'Prodigy Math', 'Epic',
107
+ 'ABCmouse', 'Starfall', 'Reading Eggs', 'Scratch', 'Codecademy', 'SoloLearn', 'Grasshopper', 'Mimo', 'Brilliant.org', 'edX',
108
+ 'FutureLearn', 'LinkedIn Learning', 'MasterClass', 'Skillshare Learn', 'Pluralsight', 'Treehouse', 'Datacamp', 'Codewars', 'freeCodeCamp', 'CodeCombat',
109
+ 'Wolfram Alpha', 'Photomath Plus', 'Mathway', 'Symbolab', 'GeoGebra', 'Desmos', 'PrepScholar', 'Magoosh', 'Kaplan', 'Princeton Review',
110
+ 'Quizizz', 'GoNoodle', 'ABC Kids', 'PBS Kids', 'Toca Boca', 'Lightbot', 'Tynker', 'CodeSpark', 'Osmo', 'Endless Alphabet',
111
+ 'Elevate', 'Peak', 'Lumosity', 'CogniFit', 'BrainHQ', 'Fit Brains Trainer', 'Photomath Solver', 'Socratic', 'Chegg Study', 'Course Hero',
112
+ 'StuDocu', 'GradeSaver', 'SparkNotes', 'CliffsNotes', 'Grammarly', 'Hemingway Editor', 'ProWritingAid', 'Quillbot', 'Wordtune', 'Forest Study',
113
+ 'Focus Keeper', 'StudyBunny', 'Habitica Learn', 'Flashcards+', 'Brainscape', 'StudyBlue', 'Turboscribe', 'Otter.ai', 'ELSA Speak', 'Speekly',
114
+ ],
115
+ entertainment: [
116
+ 'Netflix', 'Spotify', 'YouTube', 'Disney+', 'Max', 'Twitch', 'SoundCloud', 'Apple Music', 'Hulu', 'Prime Video',
117
+ 'Peacock', 'Paramount+', 'ESPN', 'YouTube TV', 'YouTube Music', 'Pandora', 'iHeartRadio', 'TuneIn Radio', 'Deezer', 'Tidal',
118
+ 'Amazon Music', 'Audible', 'Spotify Kids', 'Podcasts', 'Overcast', 'Pocket Casts', 'Castbox', 'Stitcher', 'Apple Podcasts', 'Google Podcasts',
119
+ 'IMDb', 'Letterboxd', 'Rotten Tomatoes', 'Fandango', 'AMC Theatres', 'Cinemark', 'Regal', 'Plex', 'Roku', 'Tubi',
120
+ 'Pluto TV', 'Crackle', 'Vudu', 'FandangoNOW', 'BritBox', 'Crunchyroll', 'Funimation', 'VRV', 'Kanopy', 'Hoopla',
121
+ 'Apple TV', 'Google TV', 'Fire TV', 'Chromecast', 'Sling TV', 'FuboTV', 'DirecTV Stream', 'Philo', 'Xumo Play', 'Freevee',
122
+ 'DAZN', 'NBA App', 'NFL App', 'MLB App', 'NHL App', 'FIFA+', 'UFC', 'WWE Network', 'Bleacher Report', 'theScore',
123
+ 'Steam Link', 'PlayStation App', 'Xbox App', 'Nintendo Switch Online', 'Epic Games', 'Discord', 'Genshin Impact', 'Among Us', 'Minecraft', 'Roblox',
124
+ 'TikTok Lite', 'Vine Camera', 'Dubsmash', 'Triller', 'Likee', 'Kwai', 'Byte', 'Clash', 'Reels', 'Snapchat Spotlight',
125
+ 'MX Player', 'JioCinema', 'Hotstar', 'Viu', 'iQIYI', 'Youku', 'Bilibili', 'WeTV', 'Rakuten Viki', 'Mubi',
126
+ ],
127
+ real_estate: [
128
+ 'Zillow', 'Redfin', 'Realtor.com', 'Trulia', 'Apartments.com', 'OfferUp', 'Nextdoor', 'Facebook', 'Craigslist', 'HomeSnap',
129
+ 'Compass', 'Homes.com', 'RentCafe', 'Zumper', 'Rent.com', 'Apartment List', 'HotPads', 'PadMapper', 'RadPad', 'ForRent.com',
130
+ 'LoopNet', 'CoStar', 'CityFeet', 'Crexi', 'Ten-X', 'Auction.com', 'HomeLight', 'Opendoor', 'Offerpad', 'Knock',
131
+ 'Better.com', 'Rocket Mortgage', 'LendingTree', 'Bankrate', 'Zillow Rentals', 'Zillow Premier Agent', 'RE/MAX', 'Keller Williams', 'Coldwell Banker', 'Century 21',
132
+ 'Sotheby\'s International Realty', 'Berkshire Hathaway HomeServices', 'eXp Realty', 'Redfin Agent', 'Movoto', 'Homesnap Pro', 'RealScout', 'Homie', 'Clever Real Estate', 'Ojo Labs',
133
+ 'Roofstock', 'Fundrise', 'RealtyMogul', 'DiversyFund', 'Groundfloor', 'PeerStreet', 'Yardi', 'AppFolio', 'Buildium', 'RentRedi',
134
+ 'TenantCloud', 'Cozy', 'RentSpree', 'ShowingTime', 'Dotloop', 'DocuSign', 'HomeAdvisor', 'Angi', 'Thumbtack', 'TaskRabbit',
135
+ 'Porch', 'Houzz', 'Sweeten', 'BuildZoom', 'Handy', 'Lawn Love', 'Nextdoor Business', 'Front Door', '2-10 Home Buyers Warranty', 'American Home Shield',
136
+ 'Trulia Rentals', 'ApartmentGuide', 'RENTCafe Resident', 'PadSplit', 'Bungalow', 'Common', 'Roomi', 'SpareRoom', 'Roomster', 'Roomies',
137
+ 'HomeToGo', 'Vrbo Owner', 'Airbnb Host', 'Furnished Finder', 'Landing', 'Blueground', 'Sonder', 'WhyHotel', 'Kasa Living', 'Zeus Living',
138
+ ],
139
+ };
140
+
141
+ async function fetchApp(name, attempt = 1) {
142
+ const url = `https://itunes.apple.com/search?term=${encodeURIComponent(name)}&country=us&entity=software&limit=1`;
143
+ const res = await fetch(url);
144
+ if (res.status === 429 && attempt <= 4) {
145
+ const backoff = attempt * 4000;
146
+ console.log(` ratelimit ${name} -> retrying in ${backoff}ms (attempt ${attempt + 1})`);
147
+ await new Promise((r) => setTimeout(r, backoff));
148
+ return fetchApp(name, attempt + 1);
149
+ }
150
+ if (!res.ok) throw new Error(`iTunes search failed for "${name}": ${res.status}`);
151
+ const data = await res.json();
152
+ const app = data.results?.[0];
153
+ if (!app) return null;
154
+ return {
155
+ id: app.trackId,
156
+ name: app.trackName,
157
+ genre: app.primaryGenreName,
158
+ icon: app.artworkUrl512,
159
+ rating: app.averageUserRating ?? null,
160
+ ratingCount: app.userRatingCount ?? null,
161
+ appStoreUrl: app.trackViewUrl,
162
+ screenshots: app.screenshotUrls || [],
163
+ ipadScreenshots: app.ipadScreenshotUrls || [],
164
+ };
165
+ }
166
+
167
+ async function buildCategory(categoryKey, appNames) {
168
+ const apps = [];
169
+ const seenIds = new Set();
170
+ for (const name of appNames) {
171
+ try {
172
+ const app = await fetchApp(name);
173
+ if (app && app.screenshots.length > 0 && !seenIds.has(app.id)) {
174
+ seenIds.add(app.id);
175
+ apps.push(app);
176
+ console.log(` ok ${name} -> ${app.screenshots.length} screenshots`);
177
+ } else if (app && seenIds.has(app.id)) {
178
+ console.log(` dup ${name} -> already have "${app.name}"`);
179
+ } else {
180
+ console.log(` skip ${name} -> not found or no screenshots`);
181
+ }
182
+ } catch (err) {
183
+ console.log(` fail ${name} -> ${err.message}`);
184
+ }
185
+ // be polite to Apple's free public API
186
+ await new Promise((r) => setTimeout(r, 1500));
187
+ }
188
+ return apps;
189
+ }
190
+
191
+ async function loadExisting() {
192
+ try {
193
+ const raw = await (await import('node:fs/promises')).readFile(OUT_PATH, 'utf8');
194
+ return JSON.parse(raw);
195
+ } catch {
196
+ return null;
197
+ }
198
+ }
199
+
200
+ async function main() {
201
+ const only = process.argv[2]; // optional: build just one category, e.g. `node build.mjs productivity`
202
+ const targets = only ? { [only]: CATEGORIES[only] } : CATEGORIES;
203
+ if (only && !CATEGORIES[only]) {
204
+ console.error(`Unknown category "${only}". Known: ${Object.keys(CATEGORIES).join(', ')}`);
205
+ process.exit(1);
206
+ }
207
+
208
+ const existing = await loadExisting();
209
+ const index = {
210
+ generatedAt: new Date().toISOString(),
211
+ source: 'Apple iTunes Search API (public, unauthenticated)',
212
+ categories: existing?.categories || {},
213
+ };
214
+
215
+ for (const [key, appNames] of Object.entries(targets)) {
216
+ console.log(`\n${key} (${appNames.length} apps)`);
217
+ index.categories[key] = await buildCategory(key, appNames);
218
+ // write after every category so a later failure never loses earlier progress
219
+ await mkdir(__dirname, { recursive: true });
220
+ await writeFile(OUT_PATH, JSON.stringify(index, null, 2));
221
+ }
222
+
223
+ console.log(`\nWrote ${OUT_PATH}`);
224
+ }
225
+
226
+ main();
@@ -0,0 +1,45 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const OUT_PATH = path.join(__dirname, 'index.json');
7
+
8
+ // [category, appId] pairs confirmed as wrong-category mismatches on manual review.
9
+ // Same app can be a correct match in one category and a mismatch in another
10
+ // (e.g. "BetterHelp" is a real health_fitness app but wrongly matched a
11
+ // real_estate search term), so removal is scoped per (category, id), not global.
12
+ const REMOVE = [
13
+ ['productivity', 6470821480], // "Zapia" matched from "Zapier" search - unrelated app
14
+ ['productivity', 1038369065], // "Flo Cycle & Period Tracker" matched from "Glo" - wrong app (correct in health_fitness)
15
+ ['productivity', 1573566111], // "Complices" matched from "Complice" - unrelated French dating app
16
+ ['shopping', 6748155184], // "Courtyard: TCG, Watches, Cards" - unrelated marketplace
17
+ ['food_delivery', 1416720539], // "Tango Reserve by AgilQuest" matched from generic "Reserve" search - enterprise workspace booking, not food
18
+ ['food_delivery', 6748155184], // "Courtyard" again - unrelated
19
+ ['real_estate', 995252384], // "BetterHelp - Therapy" matched from "Better.com" - wrong app (correct in health_fitness)
20
+ ['real_estate', 1264782561], // "Co-Star Personalized Astrology" matched from "CoStar" - astrology app, not commercial real estate data
21
+ ['real_estate', 1457119021], // "Rainbow - Ethereum Wallet" - unrelated crypto wallet
22
+ ['real_estate', 407108860], // "Cozi Family Organizer" - unrelated family calendar app
23
+ ['real_estate', 880735556], // "Gods of Olympus" matched from "Zeus Living" - unrelated mobile game
24
+ ];
25
+
26
+ const idx = JSON.parse(readFileSync(OUT_PATH, 'utf8'));
27
+ let removed = 0;
28
+ for (const [cat, id] of REMOVE) {
29
+ const before = idx.categories[cat].length;
30
+ idx.categories[cat] = idx.categories[cat].filter((a) => a.id !== id);
31
+ removed += before - idx.categories[cat].length;
32
+ }
33
+
34
+ idx.cleanedAt = new Date().toISOString();
35
+ writeFileSync(OUT_PATH, JSON.stringify(idx, null, 2));
36
+ console.log(`Removed ${removed} confirmed-mismatch entries.`);
37
+
38
+ let totalApps = 0, totalShots = 0;
39
+ for (const [cat, apps] of Object.entries(idx.categories)) {
40
+ const shots = apps.reduce((s, a) => s + a.screenshots.length, 0);
41
+ totalApps += apps.length;
42
+ totalShots += shots;
43
+ console.log(`${cat}: ${apps.length} apps, ${shots} screenshots`);
44
+ }
45
+ console.log(`\nFinal: ${totalApps} apps, ${totalShots} screenshots`);