dirpush 0.0.0-stage → 0.1.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 (895) hide show
  1. package/.agents/skills/dirpush/SKILL.md +92 -0
  2. package/.agents/skills/dirpush/references/assets.md +48 -0
  3. package/.agents/skills/dirpush/references/authentication.md +59 -0
  4. package/.agents/skills/dirpush/references/recovery.md +34 -0
  5. package/.agents/skills/dirpush/references/submissions.md +43 -0
  6. package/LICENSE +21 -0
  7. package/README.md +66 -2
  8. package/THIRD_PARTY_NOTICES.md +10 -0
  9. package/bin/dirpush.cjs +13 -0
  10. package/drizzle/0000_init.sql +59 -0
  11. package/drizzle/0001_jazzy_masked_marvel.sql +9 -0
  12. package/drizzle/0002_loose_may_parker.sql +9 -0
  13. package/drizzle/0003_modern_lilandra.sql +6 -0
  14. package/drizzle/0004_deep_master_chief.sql +11 -0
  15. package/drizzle/meta/0000_snapshot.json +404 -0
  16. package/drizzle/meta/0001_snapshot.json +466 -0
  17. package/drizzle/meta/0002_snapshot.json +527 -0
  18. package/drizzle/meta/0003_snapshot.json +566 -0
  19. package/drizzle/meta/0004_snapshot.json +642 -0
  20. package/drizzle/meta/_journal.json +41 -0
  21. package/examples/workspace/identity.yaml +6 -0
  22. package/examples/workspace/permissions.yaml +29 -0
  23. package/examples/workspace/product.yaml +29 -0
  24. package/package.json +76 -4
  25. package/registry/LICENSE +396 -0
  26. package/registry/directories/1000-tools/directory.yaml +55 -0
  27. package/registry/directories/1000-tools/playbook.md +5 -0
  28. package/registry/directories/10015-io/directory.yaml +66 -0
  29. package/registry/directories/10015-io/playbook.md +9 -0
  30. package/registry/directories/10words/directory.yaml +53 -0
  31. package/registry/directories/10words/playbook.md +8 -0
  32. package/registry/directories/600-tools/directory.yaml +48 -0
  33. package/registry/directories/600-tools/playbook.md +1 -0
  34. package/registry/directories/a1bookmarks/directory.yaml +42 -0
  35. package/registry/directories/aback-launch/directory.yaml +54 -0
  36. package/registry/directories/aback-launch/playbook.md +1 -0
  37. package/registry/directories/acid-tools/directory.yaml +51 -0
  38. package/registry/directories/acid-tools/playbook.md +8 -0
  39. package/registry/directories/agenthunter/directory.yaml +53 -0
  40. package/registry/directories/ai-agents-directory/directory.yaml +49 -0
  41. package/registry/directories/ai-agents-directory/playbook.md +6 -0
  42. package/registry/directories/ai-agents-list/directory.yaml +46 -0
  43. package/registry/directories/ai-agents-list/playbook.md +1 -0
  44. package/registry/directories/ai-agents-listing/directory.yaml +76 -0
  45. package/registry/directories/ai-directories/directory.yaml +45 -0
  46. package/registry/directories/ai-directories/playbook.md +7 -0
  47. package/registry/directories/ai-dust/directory.yaml +68 -0
  48. package/registry/directories/ai-dust/playbook.md +1 -0
  49. package/registry/directories/ai-nav/directory.yaml +54 -0
  50. package/registry/directories/ai-nav/playbook.md +1 -0
  51. package/registry/directories/ai-respo/directory.yaml +43 -0
  52. package/registry/directories/ai-respo/playbook.md +1 -0
  53. package/registry/directories/ai-superhub/directory.yaml +65 -0
  54. package/registry/directories/ai-tool-list/directory.yaml +44 -0
  55. package/registry/directories/ai-tool-list/playbook.md +1 -0
  56. package/registry/directories/ai-tool-saver/directory.yaml +52 -0
  57. package/registry/directories/ai-tool-saver/playbook.md +1 -0
  58. package/registry/directories/ai-toolex/directory.yaml +46 -0
  59. package/registry/directories/ai-toolex/playbook.md +1 -0
  60. package/registry/directories/ai-toolz-dir/directory.yaml +60 -0
  61. package/registry/directories/ai-website-directory/directory.yaml +66 -0
  62. package/registry/directories/ai-website-directory/playbook.md +1 -0
  63. package/registry/directories/ai-x-collection/directory.yaml +45 -0
  64. package/registry/directories/ai-x-collection/playbook.md +1 -0
  65. package/registry/directories/aiagentstore/directory.yaml +52 -0
  66. package/registry/directories/aiagentstore/playbook.md +1 -0
  67. package/registry/directories/aidirs-best/directory.yaml +46 -0
  68. package/registry/directories/aifindy/directory.yaml +44 -0
  69. package/registry/directories/aifindy/playbook.md +1 -0
  70. package/registry/directories/aihubs/directory.yaml +44 -0
  71. package/registry/directories/aihubs/playbook.md +5 -0
  72. package/registry/directories/aihuntlist/directory.yaml +43 -0
  73. package/registry/directories/aihuntlist/playbook.md +6 -0
  74. package/registry/directories/aihustle/directory.yaml +51 -0
  75. package/registry/directories/aihustle/playbook.md +5 -0
  76. package/registry/directories/ailaunch-space/directory.yaml +44 -0
  77. package/registry/directories/ailaunch-space/playbook.md +6 -0
  78. package/registry/directories/ailib/directory.yaml +64 -0
  79. package/registry/directories/ailib/playbook.md +1 -0
  80. package/registry/directories/aimatch-pro/directory.yaml +42 -0
  81. package/registry/directories/aimatch-pro/playbook.md +1 -0
  82. package/registry/directories/ainave/directory.yaml +71 -0
  83. package/registry/directories/ainave/playbook.md +10 -0
  84. package/registry/directories/aitechviral/directory.yaml +47 -0
  85. package/registry/directories/aitechviral/playbook.md +10 -0
  86. package/registry/directories/aitogrow/directory.yaml +52 -0
  87. package/registry/directories/aitogrow/playbook.md +6 -0
  88. package/registry/directories/aitoolhunt/directory.yaml +43 -0
  89. package/registry/directories/aitoolhunt/playbook.md +5 -0
  90. package/registry/directories/aitools-fyi/directory.yaml +52 -0
  91. package/registry/directories/aitools-inc/directory.yaml +48 -0
  92. package/registry/directories/aitools-inc/playbook.md +1 -0
  93. package/registry/directories/aitoolsdirectory/directory.yaml +73 -0
  94. package/registry/directories/aitoolsdirectory/playbook.md +8 -0
  95. package/registry/directories/aitoolshunt/directory.yaml +64 -0
  96. package/registry/directories/aitoolshunt/playbook.md +6 -0
  97. package/registry/directories/aitoolslist/directory.yaml +59 -0
  98. package/registry/directories/aitoolslist/playbook.md +6 -0
  99. package/registry/directories/aitoolsmagazine/directory.yaml +46 -0
  100. package/registry/directories/aitoolsmagazine/playbook.md +5 -0
  101. package/registry/directories/aitoolsmarketer/directory.yaml +56 -0
  102. package/registry/directories/aitoolsmarketer/playbook.md +7 -0
  103. package/registry/directories/aitooltrek/directory.yaml +47 -0
  104. package/registry/directories/aitooltrek/playbook.md +6 -0
  105. package/registry/directories/aitop10-tools/directory.yaml +44 -0
  106. package/registry/directories/aitop10-tools/playbook.md +5 -0
  107. package/registry/directories/aitoptools/directory.yaml +74 -0
  108. package/registry/directories/aitoptools/playbook.md +1 -0
  109. package/registry/directories/aiwithme/directory.yaml +54 -0
  110. package/registry/directories/aiwithme/playbook.md +10 -0
  111. package/registry/directories/all-in-ai-tools/directory.yaml +51 -0
  112. package/registry/directories/all-in-ai-tools/playbook.md +1 -0
  113. package/registry/directories/allmcps/directory.yaml +55 -0
  114. package/registry/directories/allmcps/playbook.md +7 -0
  115. package/registry/directories/alltopstartups/directory.yaml +43 -0
  116. package/registry/directories/altern-ai/directory.yaml +50 -0
  117. package/registry/directories/altern-ai/playbook.md +6 -0
  118. package/registry/directories/alternative-tools/directory.yaml +46 -0
  119. package/registry/directories/alternative-tools/playbook.md +4 -0
  120. package/registry/directories/alternativeto/directory.yaml +48 -0
  121. package/registry/directories/alternativeto/playbook.md +8 -0
  122. package/registry/directories/ant-directory/directory.yaml +52 -0
  123. package/registry/directories/ant-directory/playbook.md +1 -0
  124. package/registry/directories/app-scout/directory.yaml +60 -0
  125. package/registry/directories/app-scout/playbook.md +5 -0
  126. package/registry/directories/app-stack-builder/directory.yaml +71 -0
  127. package/registry/directories/appalist/directory.yaml +46 -0
  128. package/registry/directories/appalist/playbook.md +4 -0
  129. package/registry/directories/apprater/directory.yaml +65 -0
  130. package/registry/directories/apprater/playbook.md +1 -0
  131. package/registry/directories/appsumo/directory.yaml +45 -0
  132. package/registry/directories/appsumo/playbook.md +1 -0
  133. package/registry/directories/ask-ai-for-it/directory.yaml +45 -0
  134. package/registry/directories/ask-ai-for-it/playbook.md +1 -0
  135. package/registry/directories/auraplusplus/directory.yaml +56 -0
  136. package/registry/directories/auraplusplus/playbook.md +6 -0
  137. package/registry/directories/awesome-mcp-list/directory.yaml +45 -0
  138. package/registry/directories/awesome-mcp-list/playbook.md +7 -0
  139. package/registry/directories/awesome-mcp-lists/directory.yaml +44 -0
  140. package/registry/directories/awesome-mcp-lists/playbook.md +6 -0
  141. package/registry/directories/awesome-remote-mcp-servers/directory.yaml +46 -0
  142. package/registry/directories/awesome-remote-mcp-servers/playbook.md +8 -0
  143. package/registry/directories/awesome-tools/directory.yaml +55 -0
  144. package/registry/directories/awesome-tools/playbook.md +8 -0
  145. package/registry/directories/awesomeindie/directory.yaml +51 -0
  146. package/registry/directories/awesomeindie/playbook.md +7 -0
  147. package/registry/directories/axis-launch/directory.yaml +64 -0
  148. package/registry/directories/b2b-saas-market/directory.yaml +63 -0
  149. package/registry/directories/bai-tools/directory.yaml +53 -0
  150. package/registry/directories/bai-tools/playbook.md +7 -0
  151. package/registry/directories/basedtools/directory.yaml +59 -0
  152. package/registry/directories/basedtools/playbook.md +1 -0
  153. package/registry/directories/best-ai-org/directory.yaml +53 -0
  154. package/registry/directories/best-ai-org/playbook.md +1 -0
  155. package/registry/directories/best-of-ai/directory.yaml +57 -0
  156. package/registry/directories/best-of-ai/playbook.md +1 -0
  157. package/registry/directories/bestaito/directory.yaml +47 -0
  158. package/registry/directories/bestaito/playbook.md +6 -0
  159. package/registry/directories/bestofai/directory.yaml +45 -0
  160. package/registry/directories/bestofai/playbook.md +7 -0
  161. package/registry/directories/bestsky-tools/directory.yaml +44 -0
  162. package/registry/directories/bestsky-tools/playbook.md +5 -0
  163. package/registry/directories/betabound/directory.yaml +63 -0
  164. package/registry/directories/betabound/playbook.md +7 -0
  165. package/registry/directories/betalist/directory.yaml +52 -0
  166. package/registry/directories/betalist/playbook.md +11 -0
  167. package/registry/directories/betterbuys/directory.yaml +55 -0
  168. package/registry/directories/betterbuys/playbook.md +7 -0
  169. package/registry/directories/betterlaunch/directory.yaml +53 -0
  170. package/registry/directories/beyond-ai-tools/directory.yaml +43 -0
  171. package/registry/directories/beyond-ai-tools/playbook.md +1 -0
  172. package/registry/directories/billion-dollar-pitch-decks/directory.yaml +64 -0
  173. package/registry/directories/billion-dollar-pitch-decks/playbook.md +4 -0
  174. package/registry/directories/browserextension-dev/directory.yaml +55 -0
  175. package/registry/directories/browserextension-dev/playbook.md +7 -0
  176. package/registry/directories/bsdirectory/directory.yaml +68 -0
  177. package/registry/directories/bsdirectory/playbook.md +5 -0
  178. package/registry/directories/buildvoyage/directory.yaml +64 -0
  179. package/registry/directories/buildvoyage/playbook.md +1 -0
  180. package/registry/directories/built-here/directory.yaml +50 -0
  181. package/registry/directories/built-here/playbook.md +6 -0
  182. package/registry/directories/builtbyme/directory.yaml +55 -0
  183. package/registry/directories/builtbyme/playbook.md +7 -0
  184. package/registry/directories/builtineu/directory.yaml +70 -0
  185. package/registry/directories/builtineu/playbook.md +7 -0
  186. package/registry/directories/businesssoftware/directory.yaml +42 -0
  187. package/registry/directories/buy-sell-startups/directory.yaml +52 -0
  188. package/registry/directories/buy-sell-startups/playbook.md +5 -0
  189. package/registry/directories/claude-mcp-directory/directory.yaml +45 -0
  190. package/registry/directories/claude-mcp-directory/playbook.md +7 -0
  191. package/registry/directories/claude-plugin-directory/directory.yaml +48 -0
  192. package/registry/directories/claude-plugin-directory/playbook.md +7 -0
  193. package/registry/directories/cocoon-ai/directory.yaml +57 -0
  194. package/registry/directories/codehype/directory.yaml +68 -0
  195. package/registry/directories/codehype/playbook.md +7 -0
  196. package/registry/directories/collectai/directory.yaml +43 -0
  197. package/registry/directories/collectai/playbook.md +5 -0
  198. package/registry/directories/commune-launchpad/directory.yaml +51 -0
  199. package/registry/directories/confettisaas/directory.yaml +51 -0
  200. package/registry/directories/confettisaas/playbook.md +1 -0
  201. package/registry/directories/crowdstax/directory.yaml +54 -0
  202. package/registry/directories/crowdstax/playbook.md +6 -0
  203. package/registry/directories/crozdesk/directory.yaml +72 -0
  204. package/registry/directories/crozdesk/playbook.md +8 -0
  205. package/registry/directories/crypto-tools-directory/directory.yaml +60 -0
  206. package/registry/directories/crypto-tools-directory/playbook.md +6 -0
  207. package/registry/directories/ctrlaltship/directory.yaml +92 -0
  208. package/registry/directories/ctrlaltship/playbook.md +6 -0
  209. package/registry/directories/dachstartups/directory.yaml +49 -0
  210. package/registry/directories/dachstartups/playbook.md +4 -0
  211. package/registry/directories/dailypings/directory.yaml +51 -0
  212. package/registry/directories/dailypings/playbook.md +6 -0
  213. package/registry/directories/dang-ai/directory.yaml +44 -0
  214. package/registry/directories/dang-ai/playbook.md +1 -0
  215. package/registry/directories/daniellaunches/directory.yaml +47 -0
  216. package/registry/directories/daniellaunches/playbook.md +6 -0
  217. package/registry/directories/datalook/directory.yaml +60 -0
  218. package/registry/directories/dayslaunch/directory.yaml +46 -0
  219. package/registry/directories/dayslaunch/playbook.md +5 -0
  220. package/registry/directories/deeplaunch/directory.yaml +46 -0
  221. package/registry/directories/deeplaunch/playbook.md +6 -0
  222. package/registry/directories/desifounder-spotlight/directory.yaml +49 -0
  223. package/registry/directories/desifounder-spotlight/playbook.md +1 -0
  224. package/registry/directories/developers-digest-mcp/directory.yaml +46 -0
  225. package/registry/directories/developers-digest-mcp/playbook.md +7 -0
  226. package/registry/directories/devhunt/directory.yaml +53 -0
  227. package/registry/directories/devhunt/playbook.md +8 -0
  228. package/registry/directories/devpages/directory.yaml +62 -0
  229. package/registry/directories/devpages/playbook.md +7 -0
  230. package/registry/directories/devresourc-es/directory.yaml +49 -0
  231. package/registry/directories/devresourc-es/playbook.md +9 -0
  232. package/registry/directories/devtools-directory/directory.yaml +63 -0
  233. package/registry/directories/devtools-directory/playbook.md +7 -0
  234. package/registry/directories/dir-hub/directory.yaml +62 -0
  235. package/registry/directories/dir-hub/playbook.md +6 -0
  236. package/registry/directories/direct2app/directory.yaml +47 -0
  237. package/registry/directories/direct2app/playbook.md +5 -0
  238. package/registry/directories/directree/directory.yaml +56 -0
  239. package/registry/directories/directree/playbook.md +7 -0
  240. package/registry/directories/dirs-cc/directory.yaml +49 -0
  241. package/registry/directories/dirs-cc/playbook.md +1 -0
  242. package/registry/directories/dododirectory/directory.yaml +46 -0
  243. package/registry/directories/dododirectory/playbook.md +6 -0
  244. package/registry/directories/dofollowtools/directory.yaml +75 -0
  245. package/registry/directories/dofollowtools/playbook.md +7 -0
  246. package/registry/directories/dropyourai/directory.yaml +73 -0
  247. package/registry/directories/dynamite-ai/directory.yaml +59 -0
  248. package/registry/directories/dynamite-ai/playbook.md +1 -0
  249. package/registry/directories/earlyhunt/directory.yaml +44 -0
  250. package/registry/directories/earlyhunt/playbook.md +6 -0
  251. package/registry/directories/ebool/directory.yaml +72 -0
  252. package/registry/directories/ebool/playbook.md +8 -0
  253. package/registry/directories/eliteai-tools/directory.yaml +45 -0
  254. package/registry/directories/eliteai-tools/playbook.md +1 -0
  255. package/registry/directories/emoji-launch/directory.yaml +60 -0
  256. package/registry/directories/emoji-launch/playbook.md +1 -0
  257. package/registry/directories/enlisted/directory.yaml +48 -0
  258. package/registry/directories/enlisted/playbook.md +7 -0
  259. package/registry/directories/eu-software-alternatives/directory.yaml +87 -0
  260. package/registry/directories/eu-software-alternatives/playbook.md +5 -0
  261. package/registry/directories/eu-vetted/directory.yaml +64 -0
  262. package/registry/directories/eu-vetted/playbook.md +1 -0
  263. package/registry/directories/eubuilt/directory.yaml +77 -0
  264. package/registry/directories/eubuilt/playbook.md +5 -0
  265. package/registry/directories/euroalternative/directory.yaml +48 -0
  266. package/registry/directories/euroalternative/playbook.md +1 -0
  267. package/registry/directories/european-tech/directory.yaml +55 -0
  268. package/registry/directories/european-tech/playbook.md +5 -0
  269. package/registry/directories/europeanapps/directory.yaml +52 -0
  270. package/registry/directories/europeanapps/playbook.md +5 -0
  271. package/registry/directories/europeanstack/directory.yaml +66 -0
  272. package/registry/directories/europeanstack/playbook.md +7 -0
  273. package/registry/directories/eurotoolsindex/directory.yaml +68 -0
  274. package/registry/directories/eurotoolsindex/playbook.md +7 -0
  275. package/registry/directories/everydev-ai/directory.yaml +50 -0
  276. package/registry/directories/everydev-ai/playbook.md +6 -0
  277. package/registry/directories/faceless-directory/directory.yaml +52 -0
  278. package/registry/directories/fastd/directory.yaml +45 -0
  279. package/registry/directories/fastd/playbook.md +1 -0
  280. package/registry/directories/fastlaunch/directory.yaml +53 -0
  281. package/registry/directories/fastlaunch/playbook.md +10 -0
  282. package/registry/directories/favors/directory.yaml +72 -0
  283. package/registry/directories/favors/playbook.md +7 -0
  284. package/registry/directories/fazier/directory.yaml +44 -0
  285. package/registry/directories/fazier/playbook.md +8 -0
  286. package/registry/directories/featuredtool/directory.yaml +51 -0
  287. package/registry/directories/featuredtool/playbook.md +1 -0
  288. package/registry/directories/find-seo-tools/directory.yaml +72 -0
  289. package/registry/directories/find-seo-tools/playbook.md +1 -0
  290. package/registry/directories/findly-tools/directory.yaml +45 -0
  291. package/registry/directories/findly-tools/playbook.md +7 -0
  292. package/registry/directories/findstack/directory.yaml +46 -0
  293. package/registry/directories/findstack/playbook.md +1 -0
  294. package/registry/directories/findyoursaas/directory.yaml +47 -0
  295. package/registry/directories/findyoursaas/playbook.md +7 -0
  296. package/registry/directories/firsto/directory.yaml +54 -0
  297. package/registry/directories/firsto/playbook.md +10 -0
  298. package/registry/directories/fosspace/directory.yaml +49 -0
  299. package/registry/directories/founders-today/directory.yaml +52 -0
  300. package/registry/directories/francesaas/directory.yaml +49 -0
  301. package/registry/directories/francesaas/playbook.md +5 -0
  302. package/registry/directories/free-startup-directories/directory.yaml +70 -0
  303. package/registry/directories/free-startup-listing/directory.yaml +59 -0
  304. package/registry/directories/free-startup-listing/playbook.md +6 -0
  305. package/registry/directories/free-tools-directory/directory.yaml +55 -0
  306. package/registry/directories/free-tools-directory/playbook.md +7 -0
  307. package/registry/directories/freeaitools-net/directory.yaml +57 -0
  308. package/registry/directories/from-europe-with-love/directory.yaml +63 -0
  309. package/registry/directories/from-europe-with-love/playbook.md +5 -0
  310. package/registry/directories/fstlaunch/directory.yaml +57 -0
  311. package/registry/directories/fstlaunch/playbook.md +6 -0
  312. package/registry/directories/futuretools/directory.yaml +67 -0
  313. package/registry/directories/futuretools/playbook.md +10 -0
  314. package/registry/directories/glama/directory.yaml +46 -0
  315. package/registry/directories/glama/playbook.md +7 -0
  316. package/registry/directories/goodaitools/directory.yaml +46 -0
  317. package/registry/directories/goodaitools/playbook.md +7 -0
  318. package/registry/directories/gptbot/directory.yaml +56 -0
  319. package/registry/directories/gptdemo/directory.yaml +56 -0
  320. package/registry/directories/gptdemo/playbook.md +7 -0
  321. package/registry/directories/hicyou/directory.yaml +51 -0
  322. package/registry/directories/hicyou/playbook.md +1 -0
  323. package/registry/directories/hightechattic/directory.yaml +49 -0
  324. package/registry/directories/hightechattic/playbook.md +6 -0
  325. package/registry/directories/howtobuysaas/directory.yaml +59 -0
  326. package/registry/directories/howtobuysaas/playbook.md +1 -0
  327. package/registry/directories/hunt0/directory.yaml +81 -0
  328. package/registry/directories/hunt0/playbook.md +1 -0
  329. package/registry/directories/huntscreens/directory.yaml +56 -0
  330. package/registry/directories/huntscreens/playbook.md +11 -0
  331. package/registry/directories/huntstartup/directory.yaml +53 -0
  332. package/registry/directories/huntstartup/playbook.md +8 -0
  333. package/registry/directories/huzzler/directory.yaml +49 -0
  334. package/registry/directories/huzzler/playbook.md +7 -0
  335. package/registry/directories/hype-star/directory.yaml +57 -0
  336. package/registry/directories/hype-star/playbook.md +6 -0
  337. package/registry/directories/idea-kiln/directory.yaml +50 -0
  338. package/registry/directories/idea-kiln/playbook.md +6 -0
  339. package/registry/directories/ignlab-launch/directory.yaml +82 -0
  340. package/registry/directories/ignlab-launch/playbook.md +7 -0
  341. package/registry/directories/indie-deals/directory.yaml +48 -0
  342. package/registry/directories/indie-deals/playbook.md +8 -0
  343. package/registry/directories/indie-hustles/directory.yaml +61 -0
  344. package/registry/directories/indie-hustles/playbook.md +1 -0
  345. package/registry/directories/indie-makers-hub/directory.yaml +50 -0
  346. package/registry/directories/indie-makers-hub/playbook.md +5 -0
  347. package/registry/directories/indie-page/directory.yaml +47 -0
  348. package/registry/directories/indie-page/playbook.md +1 -0
  349. package/registry/directories/indiefounders/directory.yaml +48 -0
  350. package/registry/directories/indiefounders/playbook.md +5 -0
  351. package/registry/directories/indiehackers/directory.yaml +47 -0
  352. package/registry/directories/indiehackers/playbook.md +1 -0
  353. package/registry/directories/indiehackerstacks/directory.yaml +45 -0
  354. package/registry/directories/indiehackerstacks/playbook.md +5 -0
  355. package/registry/directories/indiehub/directory.yaml +45 -0
  356. package/registry/directories/indiehub/playbook.md +8 -0
  357. package/registry/directories/indiehunt/directory.yaml +45 -0
  358. package/registry/directories/indiehunt/playbook.md +6 -0
  359. package/registry/directories/indieneed/directory.yaml +78 -0
  360. package/registry/directories/indieneed/playbook.md +9 -0
  361. package/registry/directories/indieproducts/directory.yaml +47 -0
  362. package/registry/directories/indieproducts/playbook.md +4 -0
  363. package/registry/directories/indiesignal/directory.yaml +47 -0
  364. package/registry/directories/indietool/directory.yaml +44 -0
  365. package/registry/directories/indietool/playbook.md +1 -0
  366. package/registry/directories/indietools/directory.yaml +69 -0
  367. package/registry/directories/insidr-ai/directory.yaml +49 -0
  368. package/registry/directories/insidr-ai/playbook.md +5 -0
  369. package/registry/directories/intelligent-tools/directory.yaml +59 -0
  370. package/registry/directories/intelligent-tools/playbook.md +1 -0
  371. package/registry/directories/inventlist/directory.yaml +71 -0
  372. package/registry/directories/inventlist/playbook.md +5 -0
  373. package/registry/directories/joinly/directory.yaml +68 -0
  374. package/registry/directories/joinly/playbook.md +5 -0
  375. package/registry/directories/junkstartups/directory.yaml +72 -0
  376. package/registry/directories/junkstartups/playbook.md +1 -0
  377. package/registry/directories/justhunt/directory.yaml +55 -0
  378. package/registry/directories/justhunt/playbook.md +7 -0
  379. package/registry/directories/kittylaunch/directory.yaml +70 -0
  380. package/registry/directories/kittylaunch/playbook.md +7 -0
  381. package/registry/directories/lachief/directory.yaml +81 -0
  382. package/registry/directories/lachief/playbook.md +1 -0
  383. package/registry/directories/landbook/directory.yaml +48 -0
  384. package/registry/directories/landbook/playbook.md +1 -0
  385. package/registry/directories/launch-community/directory.yaml +73 -0
  386. package/registry/directories/launch-community/playbook.md +1 -0
  387. package/registry/directories/launch-list/directory.yaml +52 -0
  388. package/registry/directories/launch-list/playbook.md +6 -0
  389. package/registry/directories/launchaf/directory.yaml +51 -0
  390. package/registry/directories/launchbase-india/directory.yaml +53 -0
  391. package/registry/directories/launchboard/directory.yaml +50 -0
  392. package/registry/directories/launchboard/playbook.md +6 -0
  393. package/registry/directories/launchclash/directory.yaml +47 -0
  394. package/registry/directories/launchclash/playbook.md +6 -0
  395. package/registry/directories/launched-tools/directory.yaml +48 -0
  396. package/registry/directories/launchfree/directory.yaml +58 -0
  397. package/registry/directories/launchfree/playbook.md +1 -0
  398. package/registry/directories/launchigniter/directory.yaml +60 -0
  399. package/registry/directories/launchigniter/playbook.md +9 -0
  400. package/registry/directories/launchit/directory.yaml +48 -0
  401. package/registry/directories/launchit/playbook.md +1 -0
  402. package/registry/directories/launchllama/directory.yaml +53 -0
  403. package/registry/directories/launchllama/playbook.md +7 -0
  404. package/registry/directories/launchonit/directory.yaml +74 -0
  405. package/registry/directories/launchonit/playbook.md +7 -0
  406. package/registry/directories/launchpedia-tools/directory.yaml +43 -0
  407. package/registry/directories/launchpedia-tools/playbook.md +5 -0
  408. package/registry/directories/launchscaler/directory.yaml +53 -0
  409. package/registry/directories/launchtory/directory.yaml +47 -0
  410. package/registry/directories/launchtory/playbook.md +5 -0
  411. package/registry/directories/launchvault/directory.yaml +52 -0
  412. package/registry/directories/launchvault/playbook.md +5 -0
  413. package/registry/directories/launchvibe/directory.yaml +45 -0
  414. package/registry/directories/launchvibe/playbook.md +1 -0
  415. package/registry/directories/launchvoid/directory.yaml +45 -0
  416. package/registry/directories/launchvoid/playbook.md +6 -0
  417. package/registry/directories/letslaunch/directory.yaml +53 -0
  418. package/registry/directories/letslaunch/playbook.md +1 -0
  419. package/registry/directories/libhunt/directory.yaml +50 -0
  420. package/registry/directories/libhunt/playbook.md +1 -0
  421. package/registry/directories/linezine/directory.yaml +44 -0
  422. package/registry/directories/linezine/playbook.md +1 -0
  423. package/registry/directories/linkrena/directory.yaml +58 -0
  424. package/registry/directories/linkrena/playbook.md +6 -0
  425. package/registry/directories/list-maro/directory.yaml +74 -0
  426. package/registry/directories/list-your-tool/directory.yaml +76 -0
  427. package/registry/directories/list-your-tool/playbook.md +1 -0
  428. package/registry/directories/listbulb/directory.yaml +48 -0
  429. package/registry/directories/listbulb/playbook.md +1 -0
  430. package/registry/directories/listing-sh/directory.yaml +70 -0
  431. package/registry/directories/llm-relevance/directory.yaml +67 -0
  432. package/registry/directories/llm-relevance/playbook.md +1 -0
  433. package/registry/directories/llms-txt-hub/directory.yaml +47 -0
  434. package/registry/directories/llms-txt-hub/playbook.md +6 -0
  435. package/registry/directories/llms-txt-site/directory.yaml +53 -0
  436. package/registry/directories/llms-txt-site/playbook.md +7 -0
  437. package/registry/directories/macnative/directory.yaml +47 -0
  438. package/registry/directories/macnative/playbook.md +6 -0
  439. package/registry/directories/made-with-bolt/directory.yaml +58 -0
  440. package/registry/directories/made-with-bolt/playbook.md +1 -0
  441. package/registry/directories/made-with-lovable/directory.yaml +64 -0
  442. package/registry/directories/made-with-lovable/playbook.md +1 -0
  443. package/registry/directories/magicbox/directory.yaml +51 -0
  444. package/registry/directories/magicbox/playbook.md +16 -0
  445. package/registry/directories/maidensail/directory.yaml +54 -0
  446. package/registry/directories/makeitlast/directory.yaml +63 -0
  447. package/registry/directories/makeitlast/playbook.md +8 -0
  448. package/registry/directories/makerhunt/directory.yaml +56 -0
  449. package/registry/directories/makerhunt/playbook.md +1 -0
  450. package/registry/directories/makerlist/directory.yaml +49 -0
  451. package/registry/directories/makerlist/playbook.md +7 -0
  452. package/registry/directories/mapbid/directory.yaml +46 -0
  453. package/registry/directories/marketingdb/directory.yaml +50 -0
  454. package/registry/directories/marketingdb/playbook.md +8 -0
  455. package/registry/directories/marketraa/directory.yaml +63 -0
  456. package/registry/directories/marketraa/playbook.md +1 -0
  457. package/registry/directories/mcp-market/directory.yaml +49 -0
  458. package/registry/directories/mcp-market/playbook.md +7 -0
  459. package/registry/directories/mcp-repository/directory.yaml +45 -0
  460. package/registry/directories/mcp-repository/playbook.md +6 -0
  461. package/registry/directories/mcp-so/directory.yaml +45 -0
  462. package/registry/directories/mcp-so/playbook.md +6 -0
  463. package/registry/directories/mcpservers/directory.yaml +53 -0
  464. package/registry/directories/mcpservers/playbook.md +7 -0
  465. package/registry/directories/mf8/directory.yaml +53 -0
  466. package/registry/directories/mf8/playbook.md +5 -0
  467. package/registry/directories/microlaunch/directory.yaml +48 -0
  468. package/registry/directories/microlaunch/playbook.md +1 -0
  469. package/registry/directories/microsaasexamples/directory.yaml +50 -0
  470. package/registry/directories/must-tools/directory.yaml +68 -0
  471. package/registry/directories/must-tools/playbook.md +6 -0
  472. package/registry/directories/my-launch-stash/directory.yaml +47 -0
  473. package/registry/directories/my-launch-stash/playbook.md +8 -0
  474. package/registry/directories/nachonacho/directory.yaml +47 -0
  475. package/registry/directories/navfolders/directory.yaml +48 -0
  476. package/registry/directories/navfolders/playbook.md +5 -0
  477. package/registry/directories/navs/directory.yaml +66 -0
  478. package/registry/directories/navs/playbook.md +6 -0
  479. package/registry/directories/navtools-ai/directory.yaml +51 -0
  480. package/registry/directories/navtools-ai/playbook.md +1 -0
  481. package/registry/directories/neeed-directory/directory.yaml +60 -0
  482. package/registry/directories/neeed-directory/playbook.md +10 -0
  483. package/registry/directories/neonrev/directory.yaml +43 -0
  484. package/registry/directories/neonrev/playbook.md +1 -0
  485. package/registry/directories/newaiforyou/directory.yaml +50 -0
  486. package/registry/directories/newaiforyou/playbook.md +1 -0
  487. package/registry/directories/newtool/directory.yaml +51 -0
  488. package/registry/directories/newtool/playbook.md +1 -0
  489. package/registry/directories/nextbigwhat/directory.yaml +48 -0
  490. package/registry/directories/nextbigwhat/playbook.md +5 -0
  491. package/registry/directories/nextlaunch/directory.yaml +50 -0
  492. package/registry/directories/nextlaunch/playbook.md +8 -0
  493. package/registry/directories/nicklaunches/directory.yaml +70 -0
  494. package/registry/directories/nicklaunches/playbook.md +1 -0
  495. package/registry/directories/nocodefinder/directory.yaml +45 -0
  496. package/registry/directories/nocodefinder/playbook.md +6 -0
  497. package/registry/directories/nocodelist/directory.yaml +48 -0
  498. package/registry/directories/nocodelist/playbook.md +7 -0
  499. package/registry/directories/noonlaunch/directory.yaml +51 -0
  500. package/registry/directories/noonlaunch/playbook.md +6 -0
  501. package/registry/directories/nxgn-tools/directory.yaml +49 -0
  502. package/registry/directories/nxgn-tools/playbook.md +6 -0
  503. package/registry/directories/once-tools/directory.yaml +58 -0
  504. package/registry/directories/once-tools/playbook.md +1 -0
  505. package/registry/directories/open-launch/directory.yaml +46 -0
  506. package/registry/directories/open-launch/playbook.md +1 -0
  507. package/registry/directories/openalternative/directory.yaml +62 -0
  508. package/registry/directories/openalternative/playbook.md +8 -0
  509. package/registry/directories/openhunts/directory.yaml +52 -0
  510. package/registry/directories/openhunts/playbook.md +1 -0
  511. package/registry/directories/openresources/directory.yaml +59 -0
  512. package/registry/directories/openresources/playbook.md +6 -0
  513. package/registry/directories/opensaas-directory/directory.yaml +47 -0
  514. package/registry/directories/opensourcealternative-to/directory.yaml +63 -0
  515. package/registry/directories/opensourcealternative-to/playbook.md +6 -0
  516. package/registry/directories/opentools/directory.yaml +42 -0
  517. package/registry/directories/opentools/playbook.md +4 -0
  518. package/registry/directories/osssoftware/directory.yaml +66 -0
  519. package/registry/directories/osssoftware/playbook.md +5 -0
  520. package/registry/directories/oursoftware-eu/directory.yaml +69 -0
  521. package/registry/directories/oursoftware-eu/playbook.md +1 -0
  522. package/registry/directories/peerpush/directory.yaml +48 -0
  523. package/registry/directories/peerpush/playbook.md +8 -0
  524. package/registry/directories/phyco/directory.yaml +54 -0
  525. package/registry/directories/phygital-ai-library/directory.yaml +47 -0
  526. package/registry/directories/phygital-ai-library/playbook.md +6 -0
  527. package/registry/directories/pitchground/directory.yaml +47 -0
  528. package/registry/directories/pitchground/playbook.md +5 -0
  529. package/registry/directories/pitchwall/directory.yaml +47 -0
  530. package/registry/directories/pitchwall/playbook.md +1 -0
  531. package/registry/directories/postmake/directory.yaml +56 -0
  532. package/registry/directories/postmake/playbook.md +1 -0
  533. package/registry/directories/poweredbyai/directory.yaml +57 -0
  534. package/registry/directories/poweredbyai/playbook.md +1 -0
  535. package/registry/directories/pro-launch/directory.yaml +51 -0
  536. package/registry/directories/pro-launch/playbook.md +9 -0
  537. package/registry/directories/product-hunt/directory.yaml +54 -0
  538. package/registry/directories/product-hunt/playbook.md +1 -0
  539. package/registry/directories/product-rally/directory.yaml +50 -0
  540. package/registry/directories/product-rally/playbook.md +6 -0
  541. package/registry/directories/productivity-directory/directory.yaml +47 -0
  542. package/registry/directories/productivity-directory/playbook.md +7 -0
  543. package/registry/directories/productwatch/directory.yaml +51 -0
  544. package/registry/directories/productwatch/playbook.md +7 -0
  545. package/registry/directories/proofstories/directory.yaml +47 -0
  546. package/registry/directories/proofstories/playbook.md +1 -0
  547. package/registry/directories/prouct/directory.yaml +93 -0
  548. package/registry/directories/prouct/playbook.md +7 -0
  549. package/registry/directories/public-apis/directory.yaml +47 -0
  550. package/registry/directories/public-apis/playbook.md +6 -0
  551. package/registry/directories/publishyoursaas/directory.yaml +53 -0
  552. package/registry/directories/publishyoursaas/playbook.md +7 -0
  553. package/registry/directories/rankinpublic/directory.yaml +50 -0
  554. package/registry/directories/rankinpublic/playbook.md +1 -0
  555. package/registry/directories/rankyourai/directory.yaml +50 -0
  556. package/registry/directories/rankyourai/playbook.md +1 -0
  557. package/registry/directories/resource-fyi/directory.yaml +47 -0
  558. package/registry/directories/resource-fyi/playbook.md +5 -0
  559. package/registry/directories/roozna/directory.yaml +68 -0
  560. package/registry/directories/roozna/playbook.md +1 -0
  561. package/registry/directories/saas-fyi/directory.yaml +73 -0
  562. package/registry/directories/saas-fyi/playbook.md +7 -0
  563. package/registry/directories/saas-italia/directory.yaml +58 -0
  564. package/registry/directories/saas-launches/directory.yaml +47 -0
  565. package/registry/directories/saas-launches/playbook.md +6 -0
  566. package/registry/directories/saasbison/directory.yaml +44 -0
  567. package/registry/directories/saasbison/playbook.md +8 -0
  568. package/registry/directories/saascity/directory.yaml +50 -0
  569. package/registry/directories/saascubes/directory.yaml +49 -0
  570. package/registry/directories/saascubes/playbook.md +6 -0
  571. package/registry/directories/saasfame/directory.yaml +47 -0
  572. package/registry/directories/saasfame/playbook.md +6 -0
  573. package/registry/directories/saasgenius/directory.yaml +50 -0
  574. package/registry/directories/saasgenius/playbook.md +8 -0
  575. package/registry/directories/saasgrave-launches/directory.yaml +53 -0
  576. package/registry/directories/saasgrave-launches/playbook.md +7 -0
  577. package/registry/directories/saashammer/directory.yaml +53 -0
  578. package/registry/directories/saashammer/playbook.md +6 -0
  579. package/registry/directories/saashub/directory.yaml +51 -0
  580. package/registry/directories/saashub/playbook.md +9 -0
  581. package/registry/directories/saashunt/directory.yaml +43 -0
  582. package/registry/directories/saaslineup/directory.yaml +68 -0
  583. package/registry/directories/saaslineup/playbook.md +1 -0
  584. package/registry/directories/saaslist/directory.yaml +55 -0
  585. package/registry/directories/saaslist/playbook.md +5 -0
  586. package/registry/directories/saasmag-saas1000/directory.yaml +71 -0
  587. package/registry/directories/saaspage/directory.yaml +48 -0
  588. package/registry/directories/saasplace/directory.yaml +66 -0
  589. package/registry/directories/saasplace/playbook.md +1 -0
  590. package/registry/directories/saasrow/directory.yaml +51 -0
  591. package/registry/directories/saasrow/playbook.md +1 -0
  592. package/registry/directories/saassy-board/directory.yaml +54 -0
  593. package/registry/directories/saassy-board/playbook.md +1 -0
  594. package/registry/directories/saastore/directory.yaml +56 -0
  595. package/registry/directories/saastore/playbook.md +6 -0
  596. package/registry/directories/saastr-community-agents/directory.yaml +65 -0
  597. package/registry/directories/saastr-community-agents/playbook.md +8 -0
  598. package/registry/directories/saasvault/directory.yaml +48 -0
  599. package/registry/directories/saasvault/playbook.md +6 -0
  600. package/registry/directories/scrolllaunch/directory.yaml +54 -0
  601. package/registry/directories/scrolllaunch/playbook.md +8 -0
  602. package/registry/directories/seewhatnewai/directory.yaml +53 -0
  603. package/registry/directories/sellwithboost/directory.yaml +54 -0
  604. package/registry/directories/sellwithboost/playbook.md +5 -0
  605. package/registry/directories/serchen/directory.yaml +48 -0
  606. package/registry/directories/serchen/playbook.md +8 -0
  607. package/registry/directories/shinylaunch/directory.yaml +48 -0
  608. package/registry/directories/shinylaunch/playbook.md +6 -0
  609. package/registry/directories/shipyard-hq/directory.yaml +58 -0
  610. package/registry/directories/shipyard-hq/playbook.md +1 -0
  611. package/registry/directories/shipybara/directory.yaml +48 -0
  612. package/registry/directories/shipybara/playbook.md +8 -0
  613. package/registry/directories/shipycorn/directory.yaml +47 -0
  614. package/registry/directories/shipycorn/playbook.md +6 -0
  615. package/registry/directories/showmebest-ai/directory.yaml +50 -0
  616. package/registry/directories/showmebest-ai/playbook.md +1 -0
  617. package/registry/directories/showmysites/directory.yaml +52 -0
  618. package/registry/directories/showmysites/playbook.md +7 -0
  619. package/registry/directories/sidehunt/directory.yaml +56 -0
  620. package/registry/directories/sidehunt/playbook.md +1 -0
  621. package/registry/directories/sideprojectors/directory.yaml +55 -0
  622. package/registry/directories/sideprojectors/playbook.md +1 -0
  623. package/registry/directories/sideprojects/directory.yaml +52 -0
  624. package/registry/directories/sideprojects/playbook.md +1 -0
  625. package/registry/directories/sitepatent/directory.yaml +58 -0
  626. package/registry/directories/sitepatent/playbook.md +1 -0
  627. package/registry/directories/small-business-marketing-tools/directory.yaml +67 -0
  628. package/registry/directories/small-business-marketing-tools/playbook.md +1 -0
  629. package/registry/directories/smol-launch/directory.yaml +44 -0
  630. package/registry/directories/smol-launch/playbook.md +9 -0
  631. package/registry/directories/software-como-servicio/directory.yaml +64 -0
  632. package/registry/directories/software-como-servicio/playbook.md +6 -0
  633. package/registry/directories/software-finder/directory.yaml +47 -0
  634. package/registry/directories/software-finder/playbook.md +6 -0
  635. package/registry/directories/softwaresuggest/directory.yaml +68 -0
  636. package/registry/directories/softwaresuggest/playbook.md +6 -0
  637. package/registry/directories/solo-launches/directory.yaml +48 -0
  638. package/registry/directories/solo-launches/playbook.md +6 -0
  639. package/registry/directories/sololaunch/directory.yaml +69 -0
  640. package/registry/directories/sourceforge/directory.yaml +75 -0
  641. package/registry/directories/sourceforge/playbook.md +9 -0
  642. package/registry/directories/spacerrapps/directory.yaml +53 -0
  643. package/registry/directories/spacerrapps/playbook.md +6 -0
  644. package/registry/directories/speartip/directory.yaml +48 -0
  645. package/registry/directories/speartip/playbook.md +1 -0
  646. package/registry/directories/spiff-store/directory.yaml +46 -0
  647. package/registry/directories/spiff-store/playbook.md +6 -0
  648. package/registry/directories/spot-startups/directory.yaml +48 -0
  649. package/registry/directories/spotsaas/directory.yaml +45 -0
  650. package/registry/directories/squidir/directory.yaml +60 -0
  651. package/registry/directories/squidir/playbook.md +5 -0
  652. package/registry/directories/stackboo/directory.yaml +49 -0
  653. package/registry/directories/stackboo/playbook.md +6 -0
  654. package/registry/directories/stackledge/directory.yaml +84 -0
  655. package/registry/directories/stackledge/playbook.md +8 -0
  656. package/registry/directories/startup-alternatives/directory.yaml +68 -0
  657. package/registry/directories/startup-alternatives/playbook.md +5 -0
  658. package/registry/directories/startup-benchmarks/directory.yaml +59 -0
  659. package/registry/directories/startup-benchmarks/playbook.md +1 -0
  660. package/registry/directories/startup-collections/directory.yaml +56 -0
  661. package/registry/directories/startup-collections/playbook.md +5 -0
  662. package/registry/directories/startup-europe/directory.yaml +52 -0
  663. package/registry/directories/startup-europe/playbook.md +1 -0
  664. package/registry/directories/startup-fame/directory.yaml +51 -0
  665. package/registry/directories/startup-fame/playbook.md +8 -0
  666. package/registry/directories/startup-fast/directory.yaml +52 -0
  667. package/registry/directories/startup-fast/playbook.md +9 -0
  668. package/registry/directories/startup-list/directory.yaml +81 -0
  669. package/registry/directories/startup-list/playbook.md +8 -0
  670. package/registry/directories/startup-listing/directory.yaml +77 -0
  671. package/registry/directories/startup-listing/playbook.md +1 -0
  672. package/registry/directories/startup-og/directory.yaml +52 -0
  673. package/registry/directories/startup-ranking/directory.yaml +51 -0
  674. package/registry/directories/startup-ranking/playbook.md +1 -0
  675. package/registry/directories/startup-spotlight/directory.yaml +50 -0
  676. package/registry/directories/startup-spotlight/playbook.md +5 -0
  677. package/registry/directories/startup88/directory.yaml +46 -0
  678. package/registry/directories/startup88/playbook.md +1 -0
  679. package/registry/directories/startupaccelerator/directory.yaml +76 -0
  680. package/registry/directories/startupaccelerator/playbook.md +7 -0
  681. package/registry/directories/startupbase/directory.yaml +44 -0
  682. package/registry/directories/startupbase/playbook.md +1 -0
  683. package/registry/directories/startupbeats/directory.yaml +48 -0
  684. package/registry/directories/startupbeats/playbook.md +6 -0
  685. package/registry/directories/startupdirectory/directory.yaml +50 -0
  686. package/registry/directories/startupdirectory/playbook.md +1 -0
  687. package/registry/directories/startupinspire/directory.yaml +52 -0
  688. package/registry/directories/startupinspire/playbook.md +8 -0
  689. package/registry/directories/startuplibrary/directory.yaml +46 -0
  690. package/registry/directories/startuplibrary/playbook.md +1 -0
  691. package/registry/directories/startuplist-africa/directory.yaml +45 -0
  692. package/registry/directories/startuplist-africa/playbook.md +6 -0
  693. package/registry/directories/startups-gallery/directory.yaml +61 -0
  694. package/registry/directories/startups-gallery/playbook.md +5 -0
  695. package/registry/directories/startupsfm/directory.yaml +64 -0
  696. package/registry/directories/startupsfm/playbook.md +9 -0
  697. package/registry/directories/startupsfyi/directory.yaml +51 -0
  698. package/registry/directories/startupsfyi/playbook.md +4 -0
  699. package/registry/directories/startupslab/directory.yaml +61 -0
  700. package/registry/directories/startupslab/playbook.md +8 -0
  701. package/registry/directories/startuptile/directory.yaml +51 -0
  702. package/registry/directories/startuptile/playbook.md +5 -0
  703. package/registry/directories/startuptrusted/directory.yaml +46 -0
  704. package/registry/directories/startuptrusted/playbook.md +1 -0
  705. package/registry/directories/startuups/directory.yaml +50 -0
  706. package/registry/directories/steemhunt/directory.yaml +46 -0
  707. package/registry/directories/steemhunt/playbook.md +5 -0
  708. package/registry/directories/stellar-launch/directory.yaml +55 -0
  709. package/registry/directories/stellar-launch/playbook.md +1 -0
  710. package/registry/directories/stork-ai/directory.yaml +49 -0
  711. package/registry/directories/stork-ai/playbook.md +3 -0
  712. package/registry/directories/stork-mcp/directory.yaml +52 -0
  713. package/registry/directories/stork-mcp/playbook.md +3 -0
  714. package/registry/directories/submitaitools/directory.yaml +57 -0
  715. package/registry/directories/submitaitools/playbook.md +19 -0
  716. package/registry/directories/submithunt/directory.yaml +49 -0
  717. package/registry/directories/submithunt/playbook.md +9 -0
  718. package/registry/directories/submitmysaas/directory.yaml +51 -0
  719. package/registry/directories/submitmysaas/playbook.md +6 -0
  720. package/registry/directories/submitstartup/directory.yaml +73 -0
  721. package/registry/directories/sumodir/directory.yaml +47 -0
  722. package/registry/directories/sumodir/playbook.md +7 -0
  723. package/registry/directories/superlaunch/directory.yaml +64 -0
  724. package/registry/directories/superlaunch/playbook.md +7 -0
  725. package/registry/directories/techboard-australia/directory.yaml +54 -0
  726. package/registry/directories/techboard-australia/playbook.md +5 -0
  727. package/registry/directories/technologyadvice/directory.yaml +65 -0
  728. package/registry/directories/technologyadvice/playbook.md +8 -0
  729. package/registry/directories/tekpon/directory.yaml +44 -0
  730. package/registry/directories/tensorblock-mcp/directory.yaml +46 -0
  731. package/registry/directories/tensorblock-mcp/playbook.md +7 -0
  732. package/registry/directories/thankjohn/directory.yaml +42 -0
  733. package/registry/directories/thankjohn/playbook.md +1 -0
  734. package/registry/directories/that-ai-collection/directory.yaml +52 -0
  735. package/registry/directories/the-ai-library/directory.yaml +46 -0
  736. package/registry/directories/the-ai-library/playbook.md +1 -0
  737. package/registry/directories/the-next-ai/directory.yaml +62 -0
  738. package/registry/directories/the-popular-apps/directory.yaml +43 -0
  739. package/registry/directories/the-popular-apps/playbook.md +1 -0
  740. package/registry/directories/the-startup-inc/directory.yaml +90 -0
  741. package/registry/directories/the-startup-inc/playbook.md +4 -0
  742. package/registry/directories/the-startup-project/directory.yaml +67 -0
  743. package/registry/directories/the-startup-project/playbook.md +5 -0
  744. package/registry/directories/the-tool-directory/directory.yaml +75 -0
  745. package/registry/directories/thedevtoolsdir/directory.yaml +57 -0
  746. package/registry/directories/thedevtoolsdir/playbook.md +6 -0
  747. package/registry/directories/theres-an-ai/directory.yaml +44 -0
  748. package/registry/directories/theres-an-ai/playbook.md +1 -0
  749. package/registry/directories/theresanaiforthat/directory.yaml +51 -0
  750. package/registry/directories/theresanaiforthat/playbook.md +1 -0
  751. package/registry/directories/thesaasdir/directory.yaml +44 -0
  752. package/registry/directories/thesaasdir/playbook.md +6 -0
  753. package/registry/directories/tinylaunch/directory.yaml +50 -0
  754. package/registry/directories/tinylaunch/playbook.md +8 -0
  755. package/registry/directories/tinystartups/directory.yaml +61 -0
  756. package/registry/directories/tinystartups/playbook.md +6 -0
  757. package/registry/directories/tipseason/directory.yaml +68 -0
  758. package/registry/directories/tolodora/directory.yaml +59 -0
  759. package/registry/directories/tool-commons/directory.yaml +46 -0
  760. package/registry/directories/tool-commons/playbook.md +1 -0
  761. package/registry/directories/tool-directory-ai/directory.yaml +64 -0
  762. package/registry/directories/tool-directory-ai/playbook.md +5 -0
  763. package/registry/directories/tool-finder/directory.yaml +64 -0
  764. package/registry/directories/tool-finder/playbook.md +6 -0
  765. package/registry/directories/tooldirs/directory.yaml +64 -0
  766. package/registry/directories/tooldirs/playbook.md +7 -0
  767. package/registry/directories/toolfame/directory.yaml +47 -0
  768. package/registry/directories/toolfame/playbook.md +6 -0
  769. package/registry/directories/toolfio/directory.yaml +48 -0
  770. package/registry/directories/toolfio/playbook.md +7 -0
  771. package/registry/directories/toolfolio/directory.yaml +54 -0
  772. package/registry/directories/toolfolio/playbook.md +1 -0
  773. package/registry/directories/toolfound/directory.yaml +66 -0
  774. package/registry/directories/toolify/directory.yaml +52 -0
  775. package/registry/directories/toolify/playbook.md +8 -0
  776. package/registry/directories/toolkitly/directory.yaml +46 -0
  777. package/registry/directories/toolkitly/playbook.md +1 -0
  778. package/registry/directories/toollist-ai/directory.yaml +60 -0
  779. package/registry/directories/toollist-ai/playbook.md +1 -0
  780. package/registry/directories/toolomio/directory.yaml +55 -0
  781. package/registry/directories/toolomio/playbook.md +5 -0
  782. package/registry/directories/toolpasta/directory.yaml +46 -0
  783. package/registry/directories/toolpasta/playbook.md +1 -0
  784. package/registry/directories/toolpilot-ai/directory.yaml +77 -0
  785. package/registry/directories/toolpilot-ai/playbook.md +8 -0
  786. package/registry/directories/toolpromote/directory.yaml +81 -0
  787. package/registry/directories/toolrain/directory.yaml +48 -0
  788. package/registry/directories/toolrain/playbook.md +1 -0
  789. package/registry/directories/tools-ai-online/directory.yaml +65 -0
  790. package/registry/directories/tools-ai-online/playbook.md +7 -0
  791. package/registry/directories/tools-cafe/directory.yaml +51 -0
  792. package/registry/directories/tools-cafe/playbook.md +6 -0
  793. package/registry/directories/toolscout/directory.yaml +45 -0
  794. package/registry/directories/toolscout/playbook.md +5 -0
  795. package/registry/directories/toolsfine/directory.yaml +58 -0
  796. package/registry/directories/toolsfine/playbook.md +9 -0
  797. package/registry/directories/toolsnocode/directory.yaml +48 -0
  798. package/registry/directories/toolsnocode/playbook.md +6 -0
  799. package/registry/directories/tooltrove/directory.yaml +71 -0
  800. package/registry/directories/tooltrove/playbook.md +9 -0
  801. package/registry/directories/toolveyor/directory.yaml +44 -0
  802. package/registry/directories/toolveyor/playbook.md +1 -0
  803. package/registry/directories/toolwise/directory.yaml +67 -0
  804. package/registry/directories/toolwise/playbook.md +7 -0
  805. package/registry/directories/toools-design/directory.yaml +64 -0
  806. package/registry/directories/toools-design/playbook.md +8 -0
  807. package/registry/directories/topai-tools/directory.yaml +43 -0
  808. package/registry/directories/topai-tools/playbook.md +1 -0
  809. package/registry/directories/traffiklist/directory.yaml +55 -0
  810. package/registry/directories/traffiklist/playbook.md +6 -0
  811. package/registry/directories/trendy-startups/directory.yaml +49 -0
  812. package/registry/directories/trendy-startups/playbook.md +1 -0
  813. package/registry/directories/trillionagent/directory.yaml +57 -0
  814. package/registry/directories/trillionagent/playbook.md +1 -0
  815. package/registry/directories/truerev/directory.yaml +83 -0
  816. package/registry/directories/truerev/playbook.md +12 -0
  817. package/registry/directories/trustmrr/directory.yaml +43 -0
  818. package/registry/directories/trustradius/directory.yaml +67 -0
  819. package/registry/directories/trustradius/playbook.md +9 -0
  820. package/registry/directories/trylaunch/directory.yaml +51 -0
  821. package/registry/directories/trylaunch/playbook.md +1 -0
  822. package/registry/directories/tulimoa/directory.yaml +65 -0
  823. package/registry/directories/tulimoa/playbook.md +7 -0
  824. package/registry/directories/turbo0/directory.yaml +49 -0
  825. package/registry/directories/turbo0/playbook.md +9 -0
  826. package/registry/directories/twelve-tools/directory.yaml +47 -0
  827. package/registry/directories/twelve-tools/playbook.md +6 -0
  828. package/registry/directories/ultimate-free-tools/directory.yaml +49 -0
  829. package/registry/directories/ultimate-free-tools/playbook.md +8 -0
  830. package/registry/directories/uneed/directory.yaml +55 -0
  831. package/registry/directories/uneed/playbook.md +9 -0
  832. package/registry/directories/unitelist/directory.yaml +49 -0
  833. package/registry/directories/unitelist/playbook.md +7 -0
  834. package/registry/directories/uno-directory/directory.yaml +48 -0
  835. package/registry/directories/uno-directory/playbook.md +1 -0
  836. package/registry/directories/ventureradar/directory.yaml +47 -0
  837. package/registry/directories/ventureradar/playbook.md +5 -0
  838. package/registry/directories/vibe-coding-cloud/directory.yaml +45 -0
  839. package/registry/directories/vibe-coding-cloud/playbook.md +6 -0
  840. package/registry/directories/victrays/directory.yaml +55 -0
  841. package/registry/directories/victrays/playbook.md +8 -0
  842. package/registry/directories/visalytica/directory.yaml +59 -0
  843. package/registry/directories/wavel/directory.yaml +51 -0
  844. package/registry/directories/we-are-founders/directory.yaml +85 -0
  845. package/registry/directories/we-are-founders/playbook.md +1 -0
  846. package/registry/directories/web-store-extensions/directory.yaml +45 -0
  847. package/registry/directories/web-store-extensions/playbook.md +6 -0
  848. package/registry/directories/webcurate/directory.yaml +50 -0
  849. package/registry/directories/website-rating/directory.yaml +66 -0
  850. package/registry/directories/website-rating/playbook.md +9 -0
  851. package/registry/directories/webspot/directory.yaml +46 -0
  852. package/registry/directories/webspot/playbook.md +5 -0
  853. package/registry/directories/weekhack/directory.yaml +56 -0
  854. package/registry/directories/weekhack/playbook.md +5 -0
  855. package/registry/directories/weliketools/directory.yaml +50 -0
  856. package/registry/directories/weliketools/playbook.md +6 -0
  857. package/registry/directories/what-is-ai-tools/directory.yaml +48 -0
  858. package/registry/directories/what-is-ai-tools/playbook.md +1 -0
  859. package/registry/directories/wired-business/directory.yaml +44 -0
  860. package/registry/directories/wired-business/playbook.md +6 -0
  861. package/registry/directories/would-you-pay/directory.yaml +49 -0
  862. package/registry/directories/would-you-pay/playbook.md +6 -0
  863. package/registry/directories/xai-plugin-marketplace/directory.yaml +46 -0
  864. package/registry/directories/xai-plugin-marketplace/playbook.md +5 -0
  865. package/registry/directories/zplatform-ai/directory.yaml +75 -0
  866. package/registry/directories/zumvu/directory.yaml +50 -0
  867. package/scripts/ab +40 -0
  868. package/scripts/check-listings +8 -0
  869. package/scripts/google-login +13 -0
  870. package/scripts/show-browser +17 -0
  871. package/src/accounts.ts +81 -0
  872. package/src/audit.ts +229 -0
  873. package/src/browser.ts +123 -0
  874. package/src/capabilities.ts +62 -0
  875. package/src/cli.ts +741 -0
  876. package/src/db/index.ts +29 -0
  877. package/src/db/schema.ts +152 -0
  878. package/src/directory-kinds.ts +12 -0
  879. package/src/import-hosted.ts +156 -0
  880. package/src/jobs.ts +270 -0
  881. package/src/listings.ts +64 -0
  882. package/src/manual.ts +26 -0
  883. package/src/product-approvals.ts +31 -0
  884. package/src/public.ts +24 -0
  885. package/src/ratings.ts +71 -0
  886. package/src/registry-updates.ts +54 -0
  887. package/src/registry.ts +101 -0
  888. package/src/review.ts +58 -0
  889. package/src/server.ts +186 -0
  890. package/src/views.ts +71 -0
  891. package/src/web/app.tsx +1026 -0
  892. package/src/web/favicon.svg +4 -0
  893. package/src/web/index.html +14 -0
  894. package/src/web/style.css +301 -0
  895. package/src/workspace.ts +97 -0
@@ -0,0 +1,64 @@
1
+ /** Is a submitted listing publicly live? Used by `submissions check`. */
2
+
3
+ const UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130 Safari/537.36";
4
+ // Some directories publish the page before approval ("the approval team will review it").
5
+ const NOT_YET = /will review it|pending (approval|review)|awaiting (approval|review)|under review/i;
6
+
7
+ export const isLive = (body: string, host: string) => body.includes(host) && !NOT_YET.test(body);
8
+
9
+ /** Path segment naming the product: "ledgerline", "ledgerline-com", "ledgerline-1234" — not "ledgerlinex". */
10
+ export const matchesSlug = (url: string, slugs: string[]) => {
11
+ const path = (() => { try { return new URL(url).pathname.toLowerCase(); } catch { return ""; } })();
12
+ return path.split("/").some((seg) => slugs.some((s) => seg === s || seg.startsWith(`${s}-`)));
13
+ };
14
+
15
+ export const locs = (xml: string) => [...xml.matchAll(/<loc>\s*([^<\s]+)\s*<\/loc>/g)].map((m) => m[1]!.replace(/&amp;/g, "&"));
16
+
17
+ const get = (url: string) =>
18
+ fetch(url, { redirect: "follow", headers: { "user-agent": UA }, signal: AbortSignal.timeout(20_000) })
19
+ .then(async (r) => ({ status: r.status, body: r.ok ? await r.text() : "" }))
20
+ .catch((e: Error) => ({ status: 0, body: "", error: e.message }));
21
+
22
+ /** Listing URL from the directory's sitemap(s), for directories with no known URL pattern. */
23
+ export async function findInSitemap(site: string, slugs: string[]) {
24
+ const origin = new URL(site).origin;
25
+ const robots = (await get(`${origin}/robots.txt`)).body;
26
+ const queue = [...robots.matchAll(/^sitemap:\s*(\S+)/gim)].map((m) => m[1]!);
27
+ if (!queue.length) queue.push(`${origin}/sitemap.xml`);
28
+ // ponytail: fetches at most 30 sitemap files per directory; huge directories may hide the listing deeper.
29
+ for (let i = 0; i < queue.length && i < 30; i++) {
30
+ const xml = (await get(queue[i]!)).body;
31
+ const urls = locs(xml);
32
+ if (/<sitemapindex/i.test(xml)) queue.push(...urls);
33
+ else {
34
+ const hit = urls.find((u) => matchesSlug(u, slugs));
35
+ if (hit) return hit;
36
+ }
37
+ }
38
+ }
39
+
40
+ /** Rendered page in a throwaway anonymous browser: client-rendered pages and sites that serve fetch a different page. */
41
+ function render(url: string, session: string) {
42
+ const ab = (...args: string[]) => Bun.spawnSync(["agent-browser", "--session", session, ...args], { stderr: "pipe", timeout: 60_000 });
43
+ if (ab("open", url).exitCode) return;
44
+ ab("wait", "2000");
45
+ const out = ab("eval", `JSON.stringify([location.href, document.body.innerText + [...document.querySelectorAll("a[href]")].map(a => a.href).join(" ")])`);
46
+ try {
47
+ let v = JSON.parse(out.stdout.toString());
48
+ if (typeof v === "string") v = JSON.parse(v);
49
+ return { finalUrl: v[0] as string, body: v[1] as string };
50
+ } catch {}
51
+ }
52
+
53
+ export async function checkListing(url: string, host: string, slugs: string[], session: string) {
54
+ const res = await get(url);
55
+ if (isLive(res.body, host)) return { live: true, how: "fetch" };
56
+ if (res.status === 404 || res.status === 410) return { live: false, how: `not yet (${res.status})` };
57
+ // Fetch saw no listing (bot wall, JS-only page, soft 404) → look like a person.
58
+ const page = render(url, session);
59
+ // A redirect to the homepage can still show the product in a "recent" list; that's not its listing.
60
+ const live = !!page && matchesSlug(page.finalUrl, slugs) && isLive(page.body, host);
61
+ return { live, how: live ? "browser" : `not yet (${res.status || ("error" in res && res.error)}, browser: ${page ? "no listing" : "failed"})` };
62
+ }
63
+
64
+ export const closeBrowser = (session: string) => Bun.spawnSync(["agent-browser", "--session", session, "close"], { stderr: "ignore" });
package/src/manual.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { Db } from "./db";
2
+ import * as J from "./jobs";
3
+ import { loadDirectories, normalizeUrl } from "./registry";
4
+
5
+ /**
6
+ * Records a listing the human made outside the agents. `target` is a registry id or any URL: a URL that
7
+ * matches a registry directory's site maps to it, anything else is tracked under its domain.
8
+ */
9
+ export async function addManualSubmission(db: Db, productId: string, target: string, outcome: J.Outcome, listingUrl?: string) {
10
+ if (!["pending", "live", "rejected"].includes(outcome)) throw new Error("outcome must be pending, live or rejected");
11
+ const dirs = await loadDirectories();
12
+ let subject = dirs.find((d) => d.id === target)?.id;
13
+ if (!subject) {
14
+ let domain: string;
15
+ try {
16
+ domain = normalizeUrl(target).domain;
17
+ } catch {
18
+ throw new Error(`${target} is neither a registry directory id nor a URL`);
19
+ }
20
+ const matches = dirs.filter((d) => normalizeUrl(d.website).domain === domain).map((d) => d.id);
21
+ if (matches.length > 1) throw new Error(`${domain} matches several directories (${matches.join(", ")}); pass the id`);
22
+ subject = matches[0] ?? domain;
23
+ }
24
+ const job = J.enqueue(db, "submission", subject, productId);
25
+ return { ...J.recordManual(db, job.id, outcome, listingUrl?.trim() || undefined), inRegistry: dirs.some((d) => d.id === subject) };
26
+ }
@@ -0,0 +1,31 @@
1
+ import { and, desc, eq, inArray } from "drizzle-orm";
2
+ import type { Db } from "./db";
3
+ import { productApprovals } from "./db/schema";
4
+
5
+ export function requestProductApproval(db: Db, input: { productId: string; action: string; message: string }) {
6
+ const existing = db.select().from(productApprovals)
7
+ .where(and(eq(productApprovals.productId, input.productId), eq(productApprovals.action, input.action), inArray(productApprovals.status, ["pending", "approved"])))
8
+ .orderBy(desc(productApprovals.id)).get();
9
+ if (existing) return existing;
10
+ return db.insert(productApprovals).values({ ...input, status: "pending" }).returning().get();
11
+ }
12
+
13
+ export function resolveProductApproval(db: Db, id: number, status: "approved" | "rejected") {
14
+ const row = db.update(productApprovals).set({ status, resolvedAt: Date.now() })
15
+ .where(and(eq(productApprovals.id, id), eq(productApprovals.status, "pending"))).returning().get();
16
+ if (!row) throw new Error(`product approval ${id} is not pending`);
17
+ return row;
18
+ }
19
+
20
+ export function approvedProductAction(db: Db, productId: string, action: string) {
21
+ return db.select().from(productApprovals)
22
+ .where(and(eq(productApprovals.productId, productId), eq(productApprovals.action, action), eq(productApprovals.status, "approved")))
23
+ .orderBy(desc(productApprovals.id)).get();
24
+ }
25
+
26
+ export function consumeProductApproval(db: Db, id: number) {
27
+ const row = db.update(productApprovals).set({ status: "used", resolvedAt: Date.now() })
28
+ .where(and(eq(productApprovals.id, id), eq(productApprovals.status, "approved"))).returning().get();
29
+ if (!row) throw new Error(`product approval ${id} is not approved`);
30
+ return row;
31
+ }
package/src/public.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The registry is shared and published: it must say what a directory is like, never what one product or one
3
+ * operator did there. These helpers drop whole sentences that mention a workspace product, an operator, or
4
+ * internal tooling.
5
+ */
6
+ const INTERNAL = /workspace\/|\bdata\/|permissions?\.yaml|add_backlink|money\.spend|external\.\w|per (your|the user'?s?) instruction|\bvault\b|launch work|bun launch|agent-browser|scripts\/ab|LAUNCH_\w+|@gmail\.com|automatic review (rejected|blocked)|evidence: data/i;
7
+
8
+ export function scrubber(productWords: string[]) {
9
+ const names = productWords.filter((w) => w.length >= 3).map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
10
+ const product = names.length ? new RegExp(`\\b(${names.join("|")})\\b`, "i") : null;
11
+ const bad = (s: string) => INTERNAL.test(s) || (product?.test(s) ?? false);
12
+ /** Keeps every sentence (or markdown line) that says nothing about a specific product or operator. */
13
+ const text = (t: string) => (t.split(/(?<=[.!?])\s+|\n/).some(bad) ? clean(t) : t);
14
+ const clean = (t: string) =>
15
+ t
16
+ .split("\n")
17
+ .map((line) => line.split(/(?<=[.!?])\s+/).filter((s) => !bad(s)).join(" "))
18
+ .filter((line, i, all) => line.trim() !== "" || (i > 0 && all[i - 1]!.trim() !== "" && !/^\s*[-*]\s*$/.test(all[i - 1]!)))
19
+ .filter((line) => !/^\s*[-*]\s*$/.test(line))
20
+ .join("\n")
21
+ .replace(/\n{3,}/g, "\n\n")
22
+ .trim();
23
+ return { text, bad };
24
+ }
package/src/ratings.ts ADDED
@@ -0,0 +1,71 @@
1
+ import { desc, inArray } from "drizzle-orm";
2
+ import type { Db } from "./db";
3
+ import { domainRatings } from "./db/schema";
4
+
5
+ /** Ahrefs' free Domain Rating endpoint. Bring your own key via AHREFS_API_KEY; never a flag or file. */
6
+ const ENDPOINT = "https://api.ahrefs.com/v3/public/domain-rating-free";
7
+ const BATCH_SIZE = 80;
8
+ export const RATING_TTL = 7 * 24 * 60 * 60 * 1000;
9
+
10
+ export function ratingDomain(website: string): string {
11
+ try {
12
+ return new URL(website.includes("://") ? website : `https://${website}`).hostname.replace(/^www\./, "").toLowerCase();
13
+ } catch {
14
+ return "";
15
+ }
16
+ }
17
+
18
+ // A list hosted on a platform (a GitHub repo, a Notion page) would inherit the platform's DR, which says nothing about the list.
19
+ const PLATFORM = /^(github\.com|gitlab\.com|[\w-]+\.notion\.site|notion\.so|medium\.com|[\w-]+\.substack\.com|reddit\.com|docs\.google\.com)$/;
20
+ export const platformHosted = (website: string) => PLATFORM.test(ratingDomain(website));
21
+
22
+ /** Every reading per domain, newest first. */
23
+ export function ratingHistory(db: Db, websites: string[]) {
24
+ const domains = [...new Set(websites.map(ratingDomain).filter(Boolean))];
25
+ const rows = domains.length
26
+ ? db.select().from(domainRatings).where(inArray(domainRatings.domain, domains)).orderBy(desc(domainRatings.measuredAt)).all()
27
+ : [];
28
+ const byDomain = new Map(domains.map((d) => [d, [] as { measuredAt: number; rating: number }[]]));
29
+ for (const r of rows) byDomain.get(r.domain)!.push({ measuredAt: r.measuredAt, rating: r.rating });
30
+ return byDomain;
31
+ }
32
+
33
+ export function recordRating(db: Db, domain: string, rating: number, measuredAt: number) {
34
+ db.insert(domainRatings).values({ domain, rating, measuredAt }).onConflictDoNothing().run();
35
+ }
36
+
37
+ type Fetch = (url: string, init: RequestInit) => Promise<Response>;
38
+
39
+ /** Measures every domain not read in the last week (or all with `force`). One POST per batch of 80. */
40
+ export async function refreshRatings(
41
+ db: Db,
42
+ websites: string[],
43
+ { apiKey, force = false, now = Date.now(), fetcher = fetch as Fetch }: { apiKey: string; force?: boolean; now?: number; fetcher?: Fetch },
44
+ ) {
45
+ const history = ratingHistory(db, websites);
46
+ const due = [...history].filter(([, h]) => force || !h[0] || now - h[0].measuredAt >= RATING_TTL).map(([d]) => d);
47
+ const measured: Record<string, number> = {};
48
+ for (let i = 0; i < due.length; i += BATCH_SIZE) {
49
+ const chunk = due.slice(i, i + BATCH_SIZE);
50
+ const res = await fetcher(`${ENDPOINT}?output=json`, {
51
+ method: "POST",
52
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json", "Content-Type": "application/json" },
53
+ body: JSON.stringify({ targets: chunk, version: "dr3" }),
54
+ redirect: "manual",
55
+ signal: AbortSignal.timeout(15000),
56
+ });
57
+ if (!res.ok) {
58
+ await res.body?.cancel();
59
+ throw new Error(res.status === 401 || res.status === 403 ? "Ahrefs rejected AHREFS_API_KEY" : `Ahrefs returned HTTP ${res.status}`);
60
+ }
61
+ const data = (await res.json()) as { domain_rating?: { targets?: { target?: unknown; domain_rating?: unknown }[] } };
62
+ for (const entry of data.domain_rating?.targets ?? []) {
63
+ const domain = typeof entry.target === "string" ? ratingDomain(entry.target) : "";
64
+ const value = entry.domain_rating;
65
+ if (!chunk.includes(domain) || typeof value !== "number" || !(value >= 0 && value <= 100)) continue;
66
+ recordRating(db, domain, value, now);
67
+ measured[domain] = value;
68
+ }
69
+ }
70
+ return { checked: due.length, skipped: history.size - due.length, measured };
71
+ }
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ import { Directory, loadDirectories, loadDirectory, writeDirectory } from "./registry";
3
+
4
+ /** Where `registry updates` looks unless LAUNCH_REGISTRY_URL or --url says otherwise. Built by scripts/build-site.ts and redeployed monthly. */
5
+ export const DEFAULT_REGISTRY_URL = "https://dirpush.com/registry.json";
6
+ export const registrySource = () => process.env.LAUNCH_REGISTRY_URL ?? DEFAULT_REGISTRY_URL;
7
+
8
+ /** The published registry: every verified directory plus its playbook. Built by scripts/build-registry.ts. */
9
+ export const RegistryFile = z.object({
10
+ revision: z.string(),
11
+ generatedAt: z.string(),
12
+ directories: z.array(z.object({ directory: Directory, playbook: z.string().nullable() })),
13
+ });
14
+ export type RegistryFile = z.infer<typeof RegistryFile>;
15
+
16
+ export async function buildRegistryFile(): Promise<RegistryFile> {
17
+ const dirs = (await loadDirectories()).sort((a, b) => a.id.localeCompare(b.id));
18
+ const directories = await Promise.all(dirs.map((d) => loadDirectory(d.id)));
19
+ const digest = new Bun.CryptoHasher("sha256").update(JSON.stringify(directories)).digest("hex");
20
+ return { revision: digest.slice(0, 16), generatedAt: new Date().toISOString(), directories };
21
+ }
22
+
23
+ /** `source`: an https URL or a local path to a registry.json. */
24
+ export async function fetchRegistryFile(source: string) {
25
+ const text = /^https?:\/\//.test(source)
26
+ ? await fetch(source, { signal: AbortSignal.timeout(30000) }).then((r) => {
27
+ if (!r.ok) throw new Error(`registry download failed: HTTP ${r.status}`);
28
+ return r.text();
29
+ })
30
+ : await Bun.file(source).text();
31
+ return RegistryFile.parse(JSON.parse(text));
32
+ }
33
+
34
+ const same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b);
35
+
36
+ /**
37
+ * Compares the published registry with the local one. A local entry verified more recently than the
38
+ * published copy is kept: that is a correction you made and haven't reported yet.
39
+ */
40
+ export async function registryUpdates(remote: RegistryFile, apply = false) {
41
+ const local = new Map((await loadDirectories()).map((d) => [d.id, d]));
42
+ const added: string[] = [], changed: string[] = [], localNewer: string[] = [];
43
+ for (const { directory, playbook } of remote.directories) {
44
+ const mine = local.get(directory.id);
45
+ local.delete(directory.id);
46
+ if (!mine) added.push(directory.id);
47
+ else if (mine.verification.checked_at > directory.verification.checked_at) localNewer.push(directory.id);
48
+ else if (!same(mine, directory) || !same((await loadDirectory(directory.id)).playbook, playbook)) changed.push(directory.id);
49
+ else continue;
50
+ if (apply && !localNewer.includes(directory.id)) await writeDirectory(directory, playbook ?? undefined);
51
+ }
52
+ // Removed upstream: reported, never deleted, so nothing you rely on disappears silently.
53
+ return { revision: remote.revision, generatedAt: remote.generatedAt, applied: apply, added, changed, localNewer, onlyLocal: [...local.keys()].sort() };
54
+ }
@@ -0,0 +1,101 @@
1
+ import { cpSync, mkdirSync, readdirSync, existsSync, rmSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { z } from "zod";
4
+ import { HOME, PKG } from "./db";
5
+ import { CLASSIFICATIONS } from "./db/schema";
6
+ import { DIRECTORY_KINDS } from "./directory-kinds";
7
+
8
+ /**
9
+ * Your copy of the registry. An installed package seeds ~/.dirpush/registry from the one it ships, once; after that
10
+ * `registry updates --apply` keeps it current, and edits never touch the package.
11
+ */
12
+ export const REGISTRY = process.env.LAUNCH_REGISTRY ?? join(HOME, "registry/directories");
13
+ if (!process.env.LAUNCH_REGISTRY && HOME !== PKG && !existsSync(REGISTRY)) cpSync(join(PKG, "registry/directories"), REGISTRY, { recursive: true });
14
+
15
+ export const Directory = z.object({
16
+ id: z.string().regex(/^[a-z0-9-]+$/),
17
+ name: z.string(),
18
+ website: z.url(),
19
+ submission_url: z.url().nullable(),
20
+ cost: z.object({
21
+ classification: z.enum(CLASSIFICATIONS).exclude(["UNVERIFIED", "DEAD", "NOT_RELEVANT"]),
22
+ notes: z.string().optional(),
23
+ }),
24
+ audience: z.array(z.string()).default([]),
25
+ authentication: z.object({ required: z.boolean(), signup_allowed: z.boolean(), methods: z.array(z.string()).default([]) }),
26
+ submission: z.object({
27
+ url_first: z.boolean(),
28
+ fields: z.array(z.object({ name: z.string(), required: z.boolean(), limit: z.number().optional() })).default([]),
29
+ conditions: z.array(z.string()).default([]), // e.g. "backlink required", "manual review"
30
+ }),
31
+ autofetch: z.object({ supported: z.boolean(), expected: z.array(z.string()).default([]) }),
32
+ media: z.object({
33
+ logo: z.boolean(),
34
+ screenshots: z.object({ supported: z.boolean(), max: z.number().optional() }),
35
+ }),
36
+ // What kind of site this really is. Only "listing" is a plain directory submission; the rest need a human decision or timing.
37
+ kind: z.enum(Object.keys(DIRECTORY_KINDS) as [keyof typeof DIRECTORY_KINDS, ...(keyof typeof DIRECTORY_KINDS)[]]).optional(),
38
+ // Set when the site's bot check rejects the automated browser outright (why/where). Such directories are for the human.
39
+ human_only: z.string().nullable().optional(),
40
+ // Free-tier wait before a listing goes live, as the site states it ("instant", "3-4 months").
41
+ review_time: z.string().optional(),
42
+ // Where a listing appears once live, {slug} = product name lowercased; lets a plain script check pending listings.
43
+ listing_url_pattern: z.string().optional(),
44
+ // Measured by `registry audit` (no AI): what a listing's link is worth, read off real listing pages.
45
+ link: z
46
+ .object({
47
+ rel: z.enum(["dofollow", "nofollow", "ugc", "sponsored", "mixed", "unknown"]),
48
+ method: z.enum(["own_listing", "sample_listing", "platform_rule", "none"]),
49
+ evidence: z.string().nullable(),
50
+ samples: z.number(),
51
+ noindex: z.boolean().nullable(),
52
+ claimed: z.enum(["dofollow", "nofollow"]).nullable(),
53
+ bot_check: z.enum(["recaptcha", "turnstile", "hcaptcha", "math", "none", "unknown"]),
54
+ checked_at: z.string(),
55
+ // Why rel is still "unknown" after a real attempt: shown on the site instead of a bare "not measured".
56
+ unmeasured: z.enum(["no_listings", "no_outbound_link", "bot_wall", "login_required", "site_down"]).optional(),
57
+ })
58
+ .optional(),
59
+ verification: z.object({
60
+ status: z.literal("verified"),
61
+ checked_at: z.string(),
62
+ by: z.string().optional(),
63
+ sources: z.array(z.string()).default([]),
64
+ }),
65
+ });
66
+ export type Directory = z.infer<typeof Directory>;
67
+
68
+ export function normalizeUrl(raw: string) {
69
+ const u = new URL(raw.trim().includes("://") ? raw.trim() : `https://${raw.trim()}`);
70
+ u.hash = "";
71
+ const domain = u.hostname.toLowerCase().replace(/^www\./, "");
72
+ return { domain, url: u.toString() };
73
+ }
74
+
75
+ export async function loadDirectories(): Promise<Directory[]> {
76
+ if (!existsSync(REGISTRY)) return [];
77
+ const ids = readdirSync(REGISTRY).filter((d) => existsSync(join(REGISTRY, d, "directory.yaml")));
78
+ return Promise.all(ids.map(async (id) => Directory.parse(Bun.YAML.parse(await Bun.file(join(REGISTRY, id, "directory.yaml")).text()))));
79
+ }
80
+
81
+ export async function loadDirectory(id: string) {
82
+ const dir = join(REGISTRY, id);
83
+ if (!existsSync(join(dir, "directory.yaml"))) throw new Error(`no such directory: ${id}`);
84
+ const directory = Directory.parse(Bun.YAML.parse(await Bun.file(join(dir, "directory.yaml")).text()));
85
+ const playbook = existsSync(join(dir, "playbook.md")) ? await Bun.file(join(dir, "playbook.md")).text() : null;
86
+ return { directory, playbook };
87
+ }
88
+
89
+ export async function writeDirectory(d: Directory, playbook?: string) {
90
+ const dir = join(REGISTRY, d.id);
91
+ mkdirSync(dir, { recursive: true });
92
+ await Bun.write(join(dir, "directory.yaml"), Bun.YAML.stringify(Directory.parse(d), null, 2));
93
+ if (playbook) await Bun.write(join(dir, "playbook.md"), playbook);
94
+ }
95
+
96
+ export function removeDirectory(id: string) {
97
+ const dir = join(REGISTRY, id);
98
+ if (!/^[a-z0-9-]+$/.test(id) || !existsSync(dir)) return false;
99
+ rmSync(dir, { recursive: true });
100
+ return true;
101
+ }
package/src/review.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ import type { Db } from "./db";
3
+ import { galleries, jobs, productApprovals, registryChanges, todos } from "./db/schema";
4
+ import { logEvent, unblock } from "./jobs";
5
+
6
+ /** Human decisions, shared by the CLI and the dashboard. */
7
+ export function reviewGallery(db: Db, revision: number, status: "approved" | "rejected") {
8
+ const row = db
9
+ .update(galleries)
10
+ .set({ status })
11
+ .where(and(eq(galleries.revision, revision), eq(galleries.status, "proposed")))
12
+ .returning()
13
+ .get();
14
+ if (!row) throw new Error(`gallery revision ${revision} is not awaiting review`);
15
+ logEvent(db, null, `gallery_${status}`, "human", { revision });
16
+ // Jobs parked on this product's gallery go back to the queue; the agent reads the decision.
17
+ const waiting = db
18
+ .select({ id: jobs.id })
19
+ .from(jobs)
20
+ .where(and(eq(jobs.productId, row.productId), eq(jobs.status, "blocked"), eq(jobs.blockType, "gallery_approval")))
21
+ .all();
22
+ for (const j of waiting) unblock(db, j.id, `gallery revision ${revision} ${status}`);
23
+ return row;
24
+ }
25
+
26
+ /** Accepting only marks the proposal; an agent applies it to registry/directories/<id>/directory.yaml. */
27
+ export function reviewRegistryChange(db: Db, id: number, status: "accepted" | "rejected") {
28
+ const row = db
29
+ .update(registryChanges)
30
+ .set({ status })
31
+ .where(and(eq(registryChanges.id, id), eq(registryChanges.status, "proposed")))
32
+ .returning()
33
+ .get();
34
+ if (!row) throw new Error(`registry change ${id} is not awaiting review`);
35
+ logEvent(db, null, `registry_change_${status}`, "human", { changeId: id, directoryId: row.directoryId });
36
+ return row;
37
+ }
38
+
39
+ export function reviewProductApproval(db: Db, id: number, status: "approved" | "rejected") {
40
+ const row = db.update(productApprovals).set({ status, resolvedAt: Date.now() })
41
+ .where(and(eq(productApprovals.id, id), eq(productApprovals.status, "pending"))).returning().get();
42
+ if (!row) throw new Error(`product approval ${id} is not pending`);
43
+ logEvent(db, null, `product_approval_${status}`, "human", { approvalId: row.id, productId: row.productId, action: row.action });
44
+ return row;
45
+ }
46
+
47
+ export function addTodo(db: Db, todo: { productId?: string; jobId?: string; message: string; url?: string }) {
48
+ const row = db.insert(todos).values(todo).returning().get();
49
+ logEvent(db, todo.jobId ?? null, "todo_added", null, { todo: row.id, message: todo.message });
50
+ return row;
51
+ }
52
+
53
+ export function closeTodo(db: Db, id: number) {
54
+ const row = db.update(todos).set({ status: "done" }).where(and(eq(todos.id, id), eq(todos.status, "open"))).returning().get();
55
+ if (!row) throw new Error(`todo ${id} is not open`);
56
+ logEvent(db, row.jobId, "todo_done", "human", { todo: id });
57
+ return row;
58
+ }
package/src/server.ts ADDED
@@ -0,0 +1,186 @@
1
+ import { desc, eq, sql } from "drizzle-orm";
2
+ import { existsSync, readdirSync } from "node:fs";
3
+ import { join, resolve, sep } from "node:path";
4
+ import { HOME, openDb, PKG } from "./db";
5
+ import { events, galleries, jobs, productApprovals, registryChanges, todos } from "./db/schema";
6
+ import * as J from "./jobs";
7
+ import { loadDirectories, loadDirectory } from "./registry";
8
+ import { addManualSubmission } from "./manual";
9
+ import { platformHosted, ratingDomain, ratingHistory, refreshRatings } from "./ratings";
10
+ import { fetchRegistryFile, registrySource, registryUpdates } from "./registry-updates";
11
+ import { closeTodo, reviewGallery, reviewProductApproval, reviewRegistryChange } from "./review";
12
+ import { loadIdentity, loadPermissions, loadProduct, productIds, WORKSPACE } from "./workspace";
13
+ import index from "./web/index.html";
14
+
15
+ /** Local dashboard: observability + human decisions. Agents use the CLI, not this. */
16
+ export function serve(port: number, hostname = "127.0.0.1") {
17
+ const db = openDb();
18
+ const safe = (fn: () => unknown) => async () => {
19
+ try {
20
+ return Response.json(await fn());
21
+ } catch (e) {
22
+ return Response.json({ error: (e as Error).message }, { status: 400 });
23
+ }
24
+ };
25
+ const body = async (req: Request) => ((await req.json().catch(() => ({}))) ?? {}) as Record<string, string | undefined>;
26
+ /** Read-only files confined to `root` (screenshots for review and evidence). */
27
+ const files = (root: string, prefix: string) => (req: Request) => {
28
+ const path = resolve(root, decodeURIComponent(new URL(req.url).pathname.slice(prefix.length)));
29
+ if (!path.startsWith(resolve(root) + sep)) return new Response("forbidden", { status: 403 });
30
+ return new Response(Bun.file(path));
31
+ };
32
+ const EVIDENCE = join(HOME, "data/evidence");
33
+ const optional = async <T>(fn: () => Promise<T>) => fn().catch((e: Error) => ({ error: e.message }));
34
+
35
+ const server = Bun.serve({
36
+ port,
37
+ hostname,
38
+ development: process.env.NODE_ENV !== "production",
39
+ routes: {
40
+ "/": index,
41
+ "/api/overview": safe(() => ({
42
+ jobs: db.select({ kind: jobs.kind, status: jobs.status, n: sql<number>`count(*)` }).from(jobs).groupBy(jobs.kind, jobs.status).all(),
43
+ outcomes: db
44
+ .select({ outcome: jobs.outcome, n: sql<number>`count(*)` })
45
+ .from(jobs)
46
+ .where(eq(jobs.kind, "submission"))
47
+ .groupBy(jobs.outcome)
48
+ .all(),
49
+ activity: db.select().from(events).orderBy(desc(events.at)).limit(60).all(),
50
+ })),
51
+ "/api/actions": safe(() => ({
52
+ blocked: db.select().from(jobs).where(eq(jobs.status, "blocked")).all(),
53
+ unknown: db.select().from(jobs).where(eq(jobs.status, "unknown")).all(),
54
+ failed: db.select().from(jobs).where(eq(jobs.status, "failed")).all(),
55
+ galleries: db.select().from(galleries).where(eq(galleries.status, "proposed")).all(),
56
+ productApprovals: db.select().from(productApprovals).where(eq(productApprovals.status, "pending")).all(),
57
+ registryChanges: db.select().from(registryChanges).where(eq(registryChanges.status, "proposed")).all(),
58
+ todos: db.select().from(todos).where(eq(todos.status, "open")).all(),
59
+ })),
60
+ "/api/todos/:id/done": { POST: (req) => safe(() => closeTodo(db, Number(req.params.id)))() },
61
+ "/api/jobs": (req) =>
62
+ safe(() => {
63
+ const kind = new URL(req.url).searchParams.get("kind");
64
+ return db.select().from(jobs).where(eq(jobs.kind, kind === "research" ? "research" : "submission")).orderBy(desc(jobs.updatedAt)).all();
65
+ })(),
66
+ "/api/meta": safe(() => ({ registrySource: registrySource(), ahrefs: Boolean(process.env.AHREFS_API_KEY?.trim()) })),
67
+ // Latest DR per directory domain plus full history per product (newest first).
68
+ "/api/ratings": safe(async () => {
69
+ const products = (await Promise.all(productIds().map((id) => loadProduct(id).then((p) => ({ id, url: p.url })).catch(() => null)))).filter((p) => p !== null);
70
+ const directories = await loadDirectories();
71
+ const all = ratingHistory(db, [...products.map((p) => p.url), ...directories.map((d) => d.website)]);
72
+ return {
73
+ products: Object.fromEntries(products.map((p) => [p.id, all.get(ratingDomain(p.url)) ?? []])),
74
+ directories: Object.fromEntries(directories.map((d) => [d.id, platformHosted(d.website) ? null : all.get(ratingDomain(d.website))?.[0]?.rating ?? null])),
75
+ };
76
+ }),
77
+ "/api/ratings/refresh": {
78
+ POST: () =>
79
+ safe(async () => {
80
+ const apiKey = process.env.AHREFS_API_KEY?.trim();
81
+ if (!apiKey) throw new Error("Set AHREFS_API_KEY in the environment you start the dashboard from.");
82
+ const urls = (await Promise.all(productIds().map((id) => loadProduct(id).then((p) => p.url).catch(() => null)))).filter((u): u is string => !!u);
83
+ return refreshRatings(db, urls, { apiKey });
84
+ })(),
85
+ },
86
+ // Compares the published registry with the local one; { apply: true } writes the updates.
87
+ "/api/registry/updates": {
88
+ POST: async (req) => {
89
+ const { apply } = (await req.json().catch(() => ({}))) as { apply?: boolean };
90
+ return safe(async () => ({ source: registrySource(), checkedAt: Date.now(), ...(await registryUpdates(await fetchRegistryFile(registrySource()), apply === true)) }))();
91
+ },
92
+ },
93
+ "/api/jobs/:id": (req) =>
94
+ safe(async () => {
95
+ const job = db.select().from(jobs).where(eq(jobs.id, req.params.id)).get();
96
+ if (!job) throw new Error(`no such job: ${req.params.id}`);
97
+ const dir = join(EVIDENCE, job.id);
98
+ return {
99
+ job,
100
+ events: J.jobEvents(db, job.id),
101
+ evidence: /^[\w-]+$/.test(job.id) && existsSync(dir) ? readdirSync(dir).filter((f) => /\.(png|jpe?g|webp)$/i.test(f)).sort() : [],
102
+ directory: job.kind === "submission" ? (await optional(() => loadDirectory(job.subject).then((d) => d.directory))) : null,
103
+ };
104
+ })(),
105
+ "/api/jobs/:id/unblock": {
106
+ POST: async (req) => {
107
+ const { note, approve } = await body(req);
108
+ return safe(() => J.unblock(db, req.params.id, note?.trim() || (approve ? "approved" : "resolved via dashboard"), Date.now(), approve === "yes"))();
109
+ },
110
+ },
111
+ "/api/jobs/:id/outcome": {
112
+ POST: async (req) => {
113
+ const { outcome, listingUrl } = await body(req);
114
+ if (outcome !== "live" && outcome !== "rejected" && outcome !== "pending") return Response.json({ error: "outcome must be live, rejected or pending" }, { status: 400 });
115
+ return safe(() => J.setOutcome(db, req.params.id, outcome, listingUrl?.trim() || undefined))();
116
+ },
117
+ },
118
+ // Opens plain Chrome on Google's sign-in page (scripts/google-login). Same-origin only: another site
119
+ // in the browser must not be able to pop this open.
120
+ "/api/google-login": {
121
+ POST: (req) => {
122
+ if ((req.headers.get("sec-fetch-site") ?? "same-origin") !== "same-origin") return new Response("forbidden", { status: 403 });
123
+ Bun.spawn([join(PKG, "scripts/google-login")], { cwd: PKG, env: { ...process.env, LAUNCH_HOME: HOME }, stdout: "ignore", stderr: "ignore" }).unref();
124
+ return Response.json({ ok: true });
125
+ },
126
+ },
127
+ // Raises the agents' Chrome on the worker's tab (scripts/show-browser). Same-origin only, as above.
128
+ "/api/show-browser": {
129
+ POST: async (req) => {
130
+ if ((req.headers.get("sec-fetch-site") ?? "same-origin") !== "same-origin") return new Response("forbidden", { status: 403 });
131
+ const p = Bun.spawn([join(PKG, "scripts/show-browser")], { cwd: PKG, env: { ...process.env, LAUNCH_HOME: HOME }, stdout: "ignore", stderr: "ignore" });
132
+ return (await p.exited) === 0 ? Response.json({ ok: true }) : Response.json({ error: "agent browser is not open" }, { status: 409 });
133
+ },
134
+ },
135
+ // Runs `submissions check` (plain fetch, no AI) for one job or all pending ones; marks live listings.
136
+ "/api/submissions/check": {
137
+ POST: async (req) => {
138
+ const { job } = (await req.json().catch(() => ({}))) as { job?: string };
139
+ const p = Bun.spawn([process.execPath, join(PKG, "src/cli.ts"), "submissions", "check", ...(job ? [job] : [])], { cwd: PKG, env: { ...process.env, LAUNCH_HOME: HOME }, stdout: "pipe", stderr: "ignore" });
140
+ const out = await new Response(p.stdout).text();
141
+ return Response.json({ results: JSON.parse(out.slice(out.indexOf("["))) });
142
+ },
143
+ },
144
+ // A listing the human made themselves, from the dashboard: { product, target, outcome, listingUrl }.
145
+ "/api/submissions/add": {
146
+ POST: async (req) => {
147
+ const { product, target, outcome, listingUrl } = await body(req);
148
+ return safe(async () => addManualSubmission(db, (await loadProduct(product)).id, target ?? "", (outcome ?? "pending") as J.Outcome, listingUrl))();
149
+ },
150
+ },
151
+ // "I submitted it myself" on an existing job.
152
+ "/api/jobs/:id/manual": {
153
+ POST: async (req) => {
154
+ const { outcome, listingUrl } = await body(req);
155
+ return safe(() => J.recordManual(db, req.params.id, (outcome ?? "pending") as J.Outcome, listingUrl?.trim() || undefined))();
156
+ },
157
+ },
158
+ "/api/jobs/:id/requeue": { POST: (req) => safe(() => J.requeue(db, req.params.id))() },
159
+ "/api/galleries/:rev/:decision": {
160
+ POST: (req) => safe(() => reviewGallery(db, Number(req.params.rev), req.params.decision === "approve" ? "approved" : "rejected"))(),
161
+ },
162
+ "/api/registry-changes/:id/:decision": {
163
+ POST: (req) => safe(() => reviewRegistryChange(db, Number(req.params.id), req.params.decision === "accept" ? "accepted" : "rejected"))(),
164
+ },
165
+ "/api/product-approvals/:id/:decision": {
166
+ POST: (req) => safe(() => reviewProductApproval(db, Number(req.params.id), req.params.decision === "approve" ? "approved" : "rejected"))(),
167
+ },
168
+ "/api/registry": safe(() => loadDirectories()),
169
+ "/api/registry/:id": (req) => safe(() => loadDirectory(req.params.id))(),
170
+ "/api/products": safe(() => Promise.all(productIds().map((id) => optional(() => loadProduct(id)).then((p) => ({ id, ...p }))))),
171
+ // Workspace-wide: who agents sign up as and what they may do.
172
+ "/api/settings": safe(async () => ({ identity: await optional(() => loadIdentity()), permissions: await optional(() => loadPermissions()) })),
173
+ "/api/products/:id": (req) =>
174
+ safe(async () => ({
175
+ product: await optional(() => loadProduct(req.params.id)),
176
+ galleries: db.select().from(galleries).where(eq(galleries.productId, req.params.id)).orderBy(desc(galleries.revision)).all(),
177
+ }))(),
178
+ // Screenshots for gallery review. Confined to the workspace directory.
179
+ "/workspace/*": files(WORKSPACE, "/workspace/"),
180
+ // Agent screenshots per job: data/evidence/<job-id>/*.png
181
+ "/evidence/*": files(EVIDENCE, "/evidence/"),
182
+ },
183
+ });
184
+ console.log(`dirpush dashboard: ${server.url}`);
185
+ return server;
186
+ }