dirpush 0.0.0 → 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.
- package/.agents/skills/dirpush/SKILL.md +92 -0
- package/.agents/skills/dirpush/references/assets.md +48 -0
- package/.agents/skills/dirpush/references/authentication.md +59 -0
- package/.agents/skills/dirpush/references/recovery.md +34 -0
- package/.agents/skills/dirpush/references/submissions.md +43 -0
- package/LICENSE +21 -0
- package/README.md +63 -5
- package/THIRD_PARTY_NOTICES.md +10 -0
- package/bin/dirpush.cjs +13 -0
- package/drizzle/0000_init.sql +59 -0
- package/drizzle/0001_jazzy_masked_marvel.sql +9 -0
- package/drizzle/0002_loose_may_parker.sql +9 -0
- package/drizzle/0003_modern_lilandra.sql +6 -0
- package/drizzle/0004_deep_master_chief.sql +11 -0
- package/drizzle/meta/0000_snapshot.json +404 -0
- package/drizzle/meta/0001_snapshot.json +466 -0
- package/drizzle/meta/0002_snapshot.json +527 -0
- package/drizzle/meta/0003_snapshot.json +566 -0
- package/drizzle/meta/0004_snapshot.json +642 -0
- package/drizzle/meta/_journal.json +41 -0
- package/examples/workspace/identity.yaml +6 -0
- package/examples/workspace/permissions.yaml +29 -0
- package/examples/workspace/product.yaml +29 -0
- package/package.json +61 -6
- package/registry/LICENSE +396 -0
- package/registry/directories/1000-tools/directory.yaml +55 -0
- package/registry/directories/1000-tools/playbook.md +5 -0
- package/registry/directories/10015-io/directory.yaml +66 -0
- package/registry/directories/10015-io/playbook.md +9 -0
- package/registry/directories/10words/directory.yaml +53 -0
- package/registry/directories/10words/playbook.md +8 -0
- package/registry/directories/600-tools/directory.yaml +48 -0
- package/registry/directories/600-tools/playbook.md +1 -0
- package/registry/directories/a1bookmarks/directory.yaml +42 -0
- package/registry/directories/aback-launch/directory.yaml +54 -0
- package/registry/directories/aback-launch/playbook.md +1 -0
- package/registry/directories/acid-tools/directory.yaml +51 -0
- package/registry/directories/acid-tools/playbook.md +8 -0
- package/registry/directories/agenthunter/directory.yaml +53 -0
- package/registry/directories/ai-agents-directory/directory.yaml +49 -0
- package/registry/directories/ai-agents-directory/playbook.md +6 -0
- package/registry/directories/ai-agents-list/directory.yaml +46 -0
- package/registry/directories/ai-agents-list/playbook.md +1 -0
- package/registry/directories/ai-agents-listing/directory.yaml +76 -0
- package/registry/directories/ai-directories/directory.yaml +45 -0
- package/registry/directories/ai-directories/playbook.md +7 -0
- package/registry/directories/ai-dust/directory.yaml +68 -0
- package/registry/directories/ai-dust/playbook.md +1 -0
- package/registry/directories/ai-nav/directory.yaml +54 -0
- package/registry/directories/ai-nav/playbook.md +1 -0
- package/registry/directories/ai-respo/directory.yaml +43 -0
- package/registry/directories/ai-respo/playbook.md +1 -0
- package/registry/directories/ai-superhub/directory.yaml +65 -0
- package/registry/directories/ai-tool-list/directory.yaml +44 -0
- package/registry/directories/ai-tool-list/playbook.md +1 -0
- package/registry/directories/ai-tool-saver/directory.yaml +52 -0
- package/registry/directories/ai-tool-saver/playbook.md +1 -0
- package/registry/directories/ai-toolex/directory.yaml +46 -0
- package/registry/directories/ai-toolex/playbook.md +1 -0
- package/registry/directories/ai-toolz-dir/directory.yaml +60 -0
- package/registry/directories/ai-website-directory/directory.yaml +66 -0
- package/registry/directories/ai-website-directory/playbook.md +1 -0
- package/registry/directories/ai-x-collection/directory.yaml +45 -0
- package/registry/directories/ai-x-collection/playbook.md +1 -0
- package/registry/directories/aiagentstore/directory.yaml +52 -0
- package/registry/directories/aiagentstore/playbook.md +1 -0
- package/registry/directories/aidirs-best/directory.yaml +46 -0
- package/registry/directories/aifindy/directory.yaml +44 -0
- package/registry/directories/aifindy/playbook.md +1 -0
- package/registry/directories/aihubs/directory.yaml +44 -0
- package/registry/directories/aihubs/playbook.md +5 -0
- package/registry/directories/aihuntlist/directory.yaml +43 -0
- package/registry/directories/aihuntlist/playbook.md +6 -0
- package/registry/directories/aihustle/directory.yaml +51 -0
- package/registry/directories/aihustle/playbook.md +5 -0
- package/registry/directories/ailaunch-space/directory.yaml +44 -0
- package/registry/directories/ailaunch-space/playbook.md +6 -0
- package/registry/directories/ailib/directory.yaml +64 -0
- package/registry/directories/ailib/playbook.md +1 -0
- package/registry/directories/aimatch-pro/directory.yaml +42 -0
- package/registry/directories/aimatch-pro/playbook.md +1 -0
- package/registry/directories/ainave/directory.yaml +71 -0
- package/registry/directories/ainave/playbook.md +10 -0
- package/registry/directories/aitechviral/directory.yaml +47 -0
- package/registry/directories/aitechviral/playbook.md +10 -0
- package/registry/directories/aitogrow/directory.yaml +52 -0
- package/registry/directories/aitogrow/playbook.md +6 -0
- package/registry/directories/aitoolhunt/directory.yaml +43 -0
- package/registry/directories/aitoolhunt/playbook.md +5 -0
- package/registry/directories/aitools-fyi/directory.yaml +52 -0
- package/registry/directories/aitools-inc/directory.yaml +48 -0
- package/registry/directories/aitools-inc/playbook.md +1 -0
- package/registry/directories/aitoolsdirectory/directory.yaml +73 -0
- package/registry/directories/aitoolsdirectory/playbook.md +8 -0
- package/registry/directories/aitoolshunt/directory.yaml +64 -0
- package/registry/directories/aitoolshunt/playbook.md +6 -0
- package/registry/directories/aitoolslist/directory.yaml +59 -0
- package/registry/directories/aitoolslist/playbook.md +6 -0
- package/registry/directories/aitoolsmagazine/directory.yaml +46 -0
- package/registry/directories/aitoolsmagazine/playbook.md +5 -0
- package/registry/directories/aitoolsmarketer/directory.yaml +56 -0
- package/registry/directories/aitoolsmarketer/playbook.md +7 -0
- package/registry/directories/aitooltrek/directory.yaml +47 -0
- package/registry/directories/aitooltrek/playbook.md +6 -0
- package/registry/directories/aitop10-tools/directory.yaml +44 -0
- package/registry/directories/aitop10-tools/playbook.md +5 -0
- package/registry/directories/aitoptools/directory.yaml +74 -0
- package/registry/directories/aitoptools/playbook.md +1 -0
- package/registry/directories/aiwithme/directory.yaml +54 -0
- package/registry/directories/aiwithme/playbook.md +10 -0
- package/registry/directories/all-in-ai-tools/directory.yaml +51 -0
- package/registry/directories/all-in-ai-tools/playbook.md +1 -0
- package/registry/directories/allmcps/directory.yaml +55 -0
- package/registry/directories/allmcps/playbook.md +7 -0
- package/registry/directories/alltopstartups/directory.yaml +43 -0
- package/registry/directories/altern-ai/directory.yaml +50 -0
- package/registry/directories/altern-ai/playbook.md +6 -0
- package/registry/directories/alternative-tools/directory.yaml +46 -0
- package/registry/directories/alternative-tools/playbook.md +4 -0
- package/registry/directories/alternativeto/directory.yaml +48 -0
- package/registry/directories/alternativeto/playbook.md +8 -0
- package/registry/directories/ant-directory/directory.yaml +52 -0
- package/registry/directories/ant-directory/playbook.md +1 -0
- package/registry/directories/app-scout/directory.yaml +60 -0
- package/registry/directories/app-scout/playbook.md +5 -0
- package/registry/directories/app-stack-builder/directory.yaml +71 -0
- package/registry/directories/appalist/directory.yaml +46 -0
- package/registry/directories/appalist/playbook.md +4 -0
- package/registry/directories/apprater/directory.yaml +65 -0
- package/registry/directories/apprater/playbook.md +1 -0
- package/registry/directories/appsumo/directory.yaml +45 -0
- package/registry/directories/appsumo/playbook.md +1 -0
- package/registry/directories/ask-ai-for-it/directory.yaml +45 -0
- package/registry/directories/ask-ai-for-it/playbook.md +1 -0
- package/registry/directories/auraplusplus/directory.yaml +56 -0
- package/registry/directories/auraplusplus/playbook.md +6 -0
- package/registry/directories/awesome-mcp-list/directory.yaml +45 -0
- package/registry/directories/awesome-mcp-list/playbook.md +7 -0
- package/registry/directories/awesome-mcp-lists/directory.yaml +44 -0
- package/registry/directories/awesome-mcp-lists/playbook.md +6 -0
- package/registry/directories/awesome-remote-mcp-servers/directory.yaml +46 -0
- package/registry/directories/awesome-remote-mcp-servers/playbook.md +8 -0
- package/registry/directories/awesome-tools/directory.yaml +55 -0
- package/registry/directories/awesome-tools/playbook.md +8 -0
- package/registry/directories/awesomeindie/directory.yaml +51 -0
- package/registry/directories/awesomeindie/playbook.md +7 -0
- package/registry/directories/axis-launch/directory.yaml +64 -0
- package/registry/directories/b2b-saas-market/directory.yaml +63 -0
- package/registry/directories/bai-tools/directory.yaml +53 -0
- package/registry/directories/bai-tools/playbook.md +7 -0
- package/registry/directories/basedtools/directory.yaml +59 -0
- package/registry/directories/basedtools/playbook.md +1 -0
- package/registry/directories/best-ai-org/directory.yaml +53 -0
- package/registry/directories/best-ai-org/playbook.md +1 -0
- package/registry/directories/best-of-ai/directory.yaml +57 -0
- package/registry/directories/best-of-ai/playbook.md +1 -0
- package/registry/directories/bestaito/directory.yaml +47 -0
- package/registry/directories/bestaito/playbook.md +6 -0
- package/registry/directories/bestofai/directory.yaml +45 -0
- package/registry/directories/bestofai/playbook.md +7 -0
- package/registry/directories/bestsky-tools/directory.yaml +44 -0
- package/registry/directories/bestsky-tools/playbook.md +5 -0
- package/registry/directories/betabound/directory.yaml +63 -0
- package/registry/directories/betabound/playbook.md +7 -0
- package/registry/directories/betalist/directory.yaml +52 -0
- package/registry/directories/betalist/playbook.md +11 -0
- package/registry/directories/betterbuys/directory.yaml +55 -0
- package/registry/directories/betterbuys/playbook.md +7 -0
- package/registry/directories/betterlaunch/directory.yaml +53 -0
- package/registry/directories/beyond-ai-tools/directory.yaml +43 -0
- package/registry/directories/beyond-ai-tools/playbook.md +1 -0
- package/registry/directories/billion-dollar-pitch-decks/directory.yaml +64 -0
- package/registry/directories/billion-dollar-pitch-decks/playbook.md +4 -0
- package/registry/directories/browserextension-dev/directory.yaml +55 -0
- package/registry/directories/browserextension-dev/playbook.md +7 -0
- package/registry/directories/bsdirectory/directory.yaml +68 -0
- package/registry/directories/bsdirectory/playbook.md +5 -0
- package/registry/directories/buildvoyage/directory.yaml +64 -0
- package/registry/directories/buildvoyage/playbook.md +1 -0
- package/registry/directories/built-here/directory.yaml +50 -0
- package/registry/directories/built-here/playbook.md +6 -0
- package/registry/directories/builtbyme/directory.yaml +55 -0
- package/registry/directories/builtbyme/playbook.md +7 -0
- package/registry/directories/builtineu/directory.yaml +70 -0
- package/registry/directories/builtineu/playbook.md +7 -0
- package/registry/directories/businesssoftware/directory.yaml +42 -0
- package/registry/directories/buy-sell-startups/directory.yaml +52 -0
- package/registry/directories/buy-sell-startups/playbook.md +5 -0
- package/registry/directories/claude-mcp-directory/directory.yaml +45 -0
- package/registry/directories/claude-mcp-directory/playbook.md +7 -0
- package/registry/directories/claude-plugin-directory/directory.yaml +48 -0
- package/registry/directories/claude-plugin-directory/playbook.md +7 -0
- package/registry/directories/cocoon-ai/directory.yaml +57 -0
- package/registry/directories/codehype/directory.yaml +68 -0
- package/registry/directories/codehype/playbook.md +7 -0
- package/registry/directories/collectai/directory.yaml +43 -0
- package/registry/directories/collectai/playbook.md +5 -0
- package/registry/directories/commune-launchpad/directory.yaml +51 -0
- package/registry/directories/confettisaas/directory.yaml +51 -0
- package/registry/directories/confettisaas/playbook.md +1 -0
- package/registry/directories/crowdstax/directory.yaml +54 -0
- package/registry/directories/crowdstax/playbook.md +6 -0
- package/registry/directories/crozdesk/directory.yaml +72 -0
- package/registry/directories/crozdesk/playbook.md +8 -0
- package/registry/directories/crypto-tools-directory/directory.yaml +60 -0
- package/registry/directories/crypto-tools-directory/playbook.md +6 -0
- package/registry/directories/ctrlaltship/directory.yaml +92 -0
- package/registry/directories/ctrlaltship/playbook.md +6 -0
- package/registry/directories/dachstartups/directory.yaml +49 -0
- package/registry/directories/dachstartups/playbook.md +4 -0
- package/registry/directories/dailypings/directory.yaml +51 -0
- package/registry/directories/dailypings/playbook.md +6 -0
- package/registry/directories/dang-ai/directory.yaml +44 -0
- package/registry/directories/dang-ai/playbook.md +1 -0
- package/registry/directories/daniellaunches/directory.yaml +47 -0
- package/registry/directories/daniellaunches/playbook.md +6 -0
- package/registry/directories/datalook/directory.yaml +60 -0
- package/registry/directories/dayslaunch/directory.yaml +46 -0
- package/registry/directories/dayslaunch/playbook.md +5 -0
- package/registry/directories/deeplaunch/directory.yaml +46 -0
- package/registry/directories/deeplaunch/playbook.md +6 -0
- package/registry/directories/desifounder-spotlight/directory.yaml +49 -0
- package/registry/directories/desifounder-spotlight/playbook.md +1 -0
- package/registry/directories/developers-digest-mcp/directory.yaml +46 -0
- package/registry/directories/developers-digest-mcp/playbook.md +7 -0
- package/registry/directories/devhunt/directory.yaml +53 -0
- package/registry/directories/devhunt/playbook.md +8 -0
- package/registry/directories/devpages/directory.yaml +62 -0
- package/registry/directories/devpages/playbook.md +7 -0
- package/registry/directories/devresourc-es/directory.yaml +49 -0
- package/registry/directories/devresourc-es/playbook.md +9 -0
- package/registry/directories/devtools-directory/directory.yaml +63 -0
- package/registry/directories/devtools-directory/playbook.md +7 -0
- package/registry/directories/dir-hub/directory.yaml +62 -0
- package/registry/directories/dir-hub/playbook.md +6 -0
- package/registry/directories/direct2app/directory.yaml +47 -0
- package/registry/directories/direct2app/playbook.md +5 -0
- package/registry/directories/directree/directory.yaml +56 -0
- package/registry/directories/directree/playbook.md +7 -0
- package/registry/directories/dirs-cc/directory.yaml +49 -0
- package/registry/directories/dirs-cc/playbook.md +1 -0
- package/registry/directories/dododirectory/directory.yaml +46 -0
- package/registry/directories/dododirectory/playbook.md +6 -0
- package/registry/directories/dofollowtools/directory.yaml +75 -0
- package/registry/directories/dofollowtools/playbook.md +7 -0
- package/registry/directories/dropyourai/directory.yaml +73 -0
- package/registry/directories/dynamite-ai/directory.yaml +59 -0
- package/registry/directories/dynamite-ai/playbook.md +1 -0
- package/registry/directories/earlyhunt/directory.yaml +44 -0
- package/registry/directories/earlyhunt/playbook.md +6 -0
- package/registry/directories/ebool/directory.yaml +72 -0
- package/registry/directories/ebool/playbook.md +8 -0
- package/registry/directories/eliteai-tools/directory.yaml +45 -0
- package/registry/directories/eliteai-tools/playbook.md +1 -0
- package/registry/directories/emoji-launch/directory.yaml +60 -0
- package/registry/directories/emoji-launch/playbook.md +1 -0
- package/registry/directories/enlisted/directory.yaml +48 -0
- package/registry/directories/enlisted/playbook.md +7 -0
- package/registry/directories/eu-software-alternatives/directory.yaml +87 -0
- package/registry/directories/eu-software-alternatives/playbook.md +5 -0
- package/registry/directories/eu-vetted/directory.yaml +64 -0
- package/registry/directories/eu-vetted/playbook.md +1 -0
- package/registry/directories/eubuilt/directory.yaml +77 -0
- package/registry/directories/eubuilt/playbook.md +5 -0
- package/registry/directories/euroalternative/directory.yaml +48 -0
- package/registry/directories/euroalternative/playbook.md +1 -0
- package/registry/directories/european-tech/directory.yaml +55 -0
- package/registry/directories/european-tech/playbook.md +5 -0
- package/registry/directories/europeanapps/directory.yaml +52 -0
- package/registry/directories/europeanapps/playbook.md +5 -0
- package/registry/directories/europeanstack/directory.yaml +66 -0
- package/registry/directories/europeanstack/playbook.md +7 -0
- package/registry/directories/eurotoolsindex/directory.yaml +68 -0
- package/registry/directories/eurotoolsindex/playbook.md +7 -0
- package/registry/directories/everydev-ai/directory.yaml +50 -0
- package/registry/directories/everydev-ai/playbook.md +6 -0
- package/registry/directories/faceless-directory/directory.yaml +52 -0
- package/registry/directories/fastd/directory.yaml +45 -0
- package/registry/directories/fastd/playbook.md +1 -0
- package/registry/directories/fastlaunch/directory.yaml +53 -0
- package/registry/directories/fastlaunch/playbook.md +10 -0
- package/registry/directories/favors/directory.yaml +72 -0
- package/registry/directories/favors/playbook.md +7 -0
- package/registry/directories/fazier/directory.yaml +44 -0
- package/registry/directories/fazier/playbook.md +8 -0
- package/registry/directories/featuredtool/directory.yaml +51 -0
- package/registry/directories/featuredtool/playbook.md +1 -0
- package/registry/directories/find-seo-tools/directory.yaml +72 -0
- package/registry/directories/find-seo-tools/playbook.md +1 -0
- package/registry/directories/findly-tools/directory.yaml +45 -0
- package/registry/directories/findly-tools/playbook.md +7 -0
- package/registry/directories/findstack/directory.yaml +46 -0
- package/registry/directories/findstack/playbook.md +1 -0
- package/registry/directories/findyoursaas/directory.yaml +47 -0
- package/registry/directories/findyoursaas/playbook.md +7 -0
- package/registry/directories/firsto/directory.yaml +54 -0
- package/registry/directories/firsto/playbook.md +10 -0
- package/registry/directories/fosspace/directory.yaml +49 -0
- package/registry/directories/founders-today/directory.yaml +52 -0
- package/registry/directories/francesaas/directory.yaml +49 -0
- package/registry/directories/francesaas/playbook.md +5 -0
- package/registry/directories/free-startup-directories/directory.yaml +70 -0
- package/registry/directories/free-startup-listing/directory.yaml +59 -0
- package/registry/directories/free-startup-listing/playbook.md +6 -0
- package/registry/directories/free-tools-directory/directory.yaml +55 -0
- package/registry/directories/free-tools-directory/playbook.md +7 -0
- package/registry/directories/freeaitools-net/directory.yaml +57 -0
- package/registry/directories/from-europe-with-love/directory.yaml +63 -0
- package/registry/directories/from-europe-with-love/playbook.md +5 -0
- package/registry/directories/fstlaunch/directory.yaml +57 -0
- package/registry/directories/fstlaunch/playbook.md +6 -0
- package/registry/directories/futuretools/directory.yaml +67 -0
- package/registry/directories/futuretools/playbook.md +10 -0
- package/registry/directories/glama/directory.yaml +46 -0
- package/registry/directories/glama/playbook.md +7 -0
- package/registry/directories/goodaitools/directory.yaml +46 -0
- package/registry/directories/goodaitools/playbook.md +7 -0
- package/registry/directories/gptbot/directory.yaml +56 -0
- package/registry/directories/gptdemo/directory.yaml +56 -0
- package/registry/directories/gptdemo/playbook.md +7 -0
- package/registry/directories/hicyou/directory.yaml +51 -0
- package/registry/directories/hicyou/playbook.md +1 -0
- package/registry/directories/hightechattic/directory.yaml +49 -0
- package/registry/directories/hightechattic/playbook.md +6 -0
- package/registry/directories/howtobuysaas/directory.yaml +59 -0
- package/registry/directories/howtobuysaas/playbook.md +1 -0
- package/registry/directories/hunt0/directory.yaml +81 -0
- package/registry/directories/hunt0/playbook.md +1 -0
- package/registry/directories/huntscreens/directory.yaml +56 -0
- package/registry/directories/huntscreens/playbook.md +11 -0
- package/registry/directories/huntstartup/directory.yaml +53 -0
- package/registry/directories/huntstartup/playbook.md +8 -0
- package/registry/directories/huzzler/directory.yaml +49 -0
- package/registry/directories/huzzler/playbook.md +7 -0
- package/registry/directories/hype-star/directory.yaml +57 -0
- package/registry/directories/hype-star/playbook.md +6 -0
- package/registry/directories/idea-kiln/directory.yaml +50 -0
- package/registry/directories/idea-kiln/playbook.md +6 -0
- package/registry/directories/ignlab-launch/directory.yaml +82 -0
- package/registry/directories/ignlab-launch/playbook.md +7 -0
- package/registry/directories/indie-deals/directory.yaml +48 -0
- package/registry/directories/indie-deals/playbook.md +8 -0
- package/registry/directories/indie-hustles/directory.yaml +61 -0
- package/registry/directories/indie-hustles/playbook.md +1 -0
- package/registry/directories/indie-makers-hub/directory.yaml +50 -0
- package/registry/directories/indie-makers-hub/playbook.md +5 -0
- package/registry/directories/indie-page/directory.yaml +47 -0
- package/registry/directories/indie-page/playbook.md +1 -0
- package/registry/directories/indiefounders/directory.yaml +48 -0
- package/registry/directories/indiefounders/playbook.md +5 -0
- package/registry/directories/indiehackers/directory.yaml +47 -0
- package/registry/directories/indiehackers/playbook.md +1 -0
- package/registry/directories/indiehackerstacks/directory.yaml +45 -0
- package/registry/directories/indiehackerstacks/playbook.md +5 -0
- package/registry/directories/indiehub/directory.yaml +45 -0
- package/registry/directories/indiehub/playbook.md +8 -0
- package/registry/directories/indiehunt/directory.yaml +45 -0
- package/registry/directories/indiehunt/playbook.md +6 -0
- package/registry/directories/indieneed/directory.yaml +78 -0
- package/registry/directories/indieneed/playbook.md +9 -0
- package/registry/directories/indieproducts/directory.yaml +47 -0
- package/registry/directories/indieproducts/playbook.md +4 -0
- package/registry/directories/indiesignal/directory.yaml +47 -0
- package/registry/directories/indietool/directory.yaml +44 -0
- package/registry/directories/indietool/playbook.md +1 -0
- package/registry/directories/indietools/directory.yaml +69 -0
- package/registry/directories/insidr-ai/directory.yaml +49 -0
- package/registry/directories/insidr-ai/playbook.md +5 -0
- package/registry/directories/intelligent-tools/directory.yaml +59 -0
- package/registry/directories/intelligent-tools/playbook.md +1 -0
- package/registry/directories/inventlist/directory.yaml +71 -0
- package/registry/directories/inventlist/playbook.md +5 -0
- package/registry/directories/joinly/directory.yaml +68 -0
- package/registry/directories/joinly/playbook.md +5 -0
- package/registry/directories/junkstartups/directory.yaml +72 -0
- package/registry/directories/junkstartups/playbook.md +1 -0
- package/registry/directories/justhunt/directory.yaml +55 -0
- package/registry/directories/justhunt/playbook.md +7 -0
- package/registry/directories/kittylaunch/directory.yaml +70 -0
- package/registry/directories/kittylaunch/playbook.md +7 -0
- package/registry/directories/lachief/directory.yaml +81 -0
- package/registry/directories/lachief/playbook.md +1 -0
- package/registry/directories/landbook/directory.yaml +48 -0
- package/registry/directories/landbook/playbook.md +1 -0
- package/registry/directories/launch-community/directory.yaml +73 -0
- package/registry/directories/launch-community/playbook.md +1 -0
- package/registry/directories/launch-list/directory.yaml +52 -0
- package/registry/directories/launch-list/playbook.md +6 -0
- package/registry/directories/launchaf/directory.yaml +51 -0
- package/registry/directories/launchbase-india/directory.yaml +53 -0
- package/registry/directories/launchboard/directory.yaml +50 -0
- package/registry/directories/launchboard/playbook.md +6 -0
- package/registry/directories/launchclash/directory.yaml +47 -0
- package/registry/directories/launchclash/playbook.md +6 -0
- package/registry/directories/launched-tools/directory.yaml +48 -0
- package/registry/directories/launchfree/directory.yaml +58 -0
- package/registry/directories/launchfree/playbook.md +1 -0
- package/registry/directories/launchigniter/directory.yaml +60 -0
- package/registry/directories/launchigniter/playbook.md +9 -0
- package/registry/directories/launchit/directory.yaml +48 -0
- package/registry/directories/launchit/playbook.md +1 -0
- package/registry/directories/launchllama/directory.yaml +53 -0
- package/registry/directories/launchllama/playbook.md +7 -0
- package/registry/directories/launchonit/directory.yaml +74 -0
- package/registry/directories/launchonit/playbook.md +7 -0
- package/registry/directories/launchpedia-tools/directory.yaml +43 -0
- package/registry/directories/launchpedia-tools/playbook.md +5 -0
- package/registry/directories/launchscaler/directory.yaml +53 -0
- package/registry/directories/launchtory/directory.yaml +47 -0
- package/registry/directories/launchtory/playbook.md +5 -0
- package/registry/directories/launchvault/directory.yaml +52 -0
- package/registry/directories/launchvault/playbook.md +5 -0
- package/registry/directories/launchvibe/directory.yaml +45 -0
- package/registry/directories/launchvibe/playbook.md +1 -0
- package/registry/directories/launchvoid/directory.yaml +45 -0
- package/registry/directories/launchvoid/playbook.md +6 -0
- package/registry/directories/letslaunch/directory.yaml +53 -0
- package/registry/directories/letslaunch/playbook.md +1 -0
- package/registry/directories/libhunt/directory.yaml +50 -0
- package/registry/directories/libhunt/playbook.md +1 -0
- package/registry/directories/linezine/directory.yaml +44 -0
- package/registry/directories/linezine/playbook.md +1 -0
- package/registry/directories/linkrena/directory.yaml +58 -0
- package/registry/directories/linkrena/playbook.md +6 -0
- package/registry/directories/list-maro/directory.yaml +74 -0
- package/registry/directories/list-your-tool/directory.yaml +76 -0
- package/registry/directories/list-your-tool/playbook.md +1 -0
- package/registry/directories/listbulb/directory.yaml +48 -0
- package/registry/directories/listbulb/playbook.md +1 -0
- package/registry/directories/listing-sh/directory.yaml +70 -0
- package/registry/directories/llm-relevance/directory.yaml +67 -0
- package/registry/directories/llm-relevance/playbook.md +1 -0
- package/registry/directories/llms-txt-hub/directory.yaml +47 -0
- package/registry/directories/llms-txt-hub/playbook.md +6 -0
- package/registry/directories/llms-txt-site/directory.yaml +53 -0
- package/registry/directories/llms-txt-site/playbook.md +7 -0
- package/registry/directories/macnative/directory.yaml +47 -0
- package/registry/directories/macnative/playbook.md +6 -0
- package/registry/directories/made-with-bolt/directory.yaml +58 -0
- package/registry/directories/made-with-bolt/playbook.md +1 -0
- package/registry/directories/made-with-lovable/directory.yaml +64 -0
- package/registry/directories/made-with-lovable/playbook.md +1 -0
- package/registry/directories/magicbox/directory.yaml +51 -0
- package/registry/directories/magicbox/playbook.md +16 -0
- package/registry/directories/maidensail/directory.yaml +54 -0
- package/registry/directories/makeitlast/directory.yaml +63 -0
- package/registry/directories/makeitlast/playbook.md +8 -0
- package/registry/directories/makerhunt/directory.yaml +56 -0
- package/registry/directories/makerhunt/playbook.md +1 -0
- package/registry/directories/makerlist/directory.yaml +49 -0
- package/registry/directories/makerlist/playbook.md +7 -0
- package/registry/directories/mapbid/directory.yaml +46 -0
- package/registry/directories/marketingdb/directory.yaml +50 -0
- package/registry/directories/marketingdb/playbook.md +8 -0
- package/registry/directories/marketraa/directory.yaml +63 -0
- package/registry/directories/marketraa/playbook.md +1 -0
- package/registry/directories/mcp-market/directory.yaml +49 -0
- package/registry/directories/mcp-market/playbook.md +7 -0
- package/registry/directories/mcp-repository/directory.yaml +45 -0
- package/registry/directories/mcp-repository/playbook.md +6 -0
- package/registry/directories/mcp-so/directory.yaml +45 -0
- package/registry/directories/mcp-so/playbook.md +6 -0
- package/registry/directories/mcpservers/directory.yaml +53 -0
- package/registry/directories/mcpservers/playbook.md +7 -0
- package/registry/directories/mf8/directory.yaml +53 -0
- package/registry/directories/mf8/playbook.md +5 -0
- package/registry/directories/microlaunch/directory.yaml +48 -0
- package/registry/directories/microlaunch/playbook.md +1 -0
- package/registry/directories/microsaasexamples/directory.yaml +50 -0
- package/registry/directories/must-tools/directory.yaml +68 -0
- package/registry/directories/must-tools/playbook.md +6 -0
- package/registry/directories/my-launch-stash/directory.yaml +47 -0
- package/registry/directories/my-launch-stash/playbook.md +8 -0
- package/registry/directories/nachonacho/directory.yaml +47 -0
- package/registry/directories/navfolders/directory.yaml +48 -0
- package/registry/directories/navfolders/playbook.md +5 -0
- package/registry/directories/navs/directory.yaml +66 -0
- package/registry/directories/navs/playbook.md +6 -0
- package/registry/directories/navtools-ai/directory.yaml +51 -0
- package/registry/directories/navtools-ai/playbook.md +1 -0
- package/registry/directories/neeed-directory/directory.yaml +60 -0
- package/registry/directories/neeed-directory/playbook.md +10 -0
- package/registry/directories/neonrev/directory.yaml +43 -0
- package/registry/directories/neonrev/playbook.md +1 -0
- package/registry/directories/newaiforyou/directory.yaml +50 -0
- package/registry/directories/newaiforyou/playbook.md +1 -0
- package/registry/directories/newtool/directory.yaml +51 -0
- package/registry/directories/newtool/playbook.md +1 -0
- package/registry/directories/nextbigwhat/directory.yaml +48 -0
- package/registry/directories/nextbigwhat/playbook.md +5 -0
- package/registry/directories/nextlaunch/directory.yaml +50 -0
- package/registry/directories/nextlaunch/playbook.md +8 -0
- package/registry/directories/nicklaunches/directory.yaml +70 -0
- package/registry/directories/nicklaunches/playbook.md +1 -0
- package/registry/directories/nocodefinder/directory.yaml +45 -0
- package/registry/directories/nocodefinder/playbook.md +6 -0
- package/registry/directories/nocodelist/directory.yaml +48 -0
- package/registry/directories/nocodelist/playbook.md +7 -0
- package/registry/directories/noonlaunch/directory.yaml +51 -0
- package/registry/directories/noonlaunch/playbook.md +6 -0
- package/registry/directories/nxgn-tools/directory.yaml +49 -0
- package/registry/directories/nxgn-tools/playbook.md +6 -0
- package/registry/directories/once-tools/directory.yaml +58 -0
- package/registry/directories/once-tools/playbook.md +1 -0
- package/registry/directories/open-launch/directory.yaml +46 -0
- package/registry/directories/open-launch/playbook.md +1 -0
- package/registry/directories/openalternative/directory.yaml +62 -0
- package/registry/directories/openalternative/playbook.md +8 -0
- package/registry/directories/openhunts/directory.yaml +52 -0
- package/registry/directories/openhunts/playbook.md +1 -0
- package/registry/directories/openresources/directory.yaml +59 -0
- package/registry/directories/openresources/playbook.md +6 -0
- package/registry/directories/opensaas-directory/directory.yaml +47 -0
- package/registry/directories/opensourcealternative-to/directory.yaml +63 -0
- package/registry/directories/opensourcealternative-to/playbook.md +6 -0
- package/registry/directories/opentools/directory.yaml +42 -0
- package/registry/directories/opentools/playbook.md +4 -0
- package/registry/directories/osssoftware/directory.yaml +66 -0
- package/registry/directories/osssoftware/playbook.md +5 -0
- package/registry/directories/oursoftware-eu/directory.yaml +69 -0
- package/registry/directories/oursoftware-eu/playbook.md +1 -0
- package/registry/directories/peerpush/directory.yaml +48 -0
- package/registry/directories/peerpush/playbook.md +8 -0
- package/registry/directories/phyco/directory.yaml +54 -0
- package/registry/directories/phygital-ai-library/directory.yaml +47 -0
- package/registry/directories/phygital-ai-library/playbook.md +6 -0
- package/registry/directories/pitchground/directory.yaml +47 -0
- package/registry/directories/pitchground/playbook.md +5 -0
- package/registry/directories/pitchwall/directory.yaml +47 -0
- package/registry/directories/pitchwall/playbook.md +1 -0
- package/registry/directories/postmake/directory.yaml +56 -0
- package/registry/directories/postmake/playbook.md +1 -0
- package/registry/directories/poweredbyai/directory.yaml +57 -0
- package/registry/directories/poweredbyai/playbook.md +1 -0
- package/registry/directories/pro-launch/directory.yaml +51 -0
- package/registry/directories/pro-launch/playbook.md +9 -0
- package/registry/directories/product-hunt/directory.yaml +54 -0
- package/registry/directories/product-hunt/playbook.md +1 -0
- package/registry/directories/product-rally/directory.yaml +50 -0
- package/registry/directories/product-rally/playbook.md +6 -0
- package/registry/directories/productivity-directory/directory.yaml +47 -0
- package/registry/directories/productivity-directory/playbook.md +7 -0
- package/registry/directories/productwatch/directory.yaml +51 -0
- package/registry/directories/productwatch/playbook.md +7 -0
- package/registry/directories/proofstories/directory.yaml +47 -0
- package/registry/directories/proofstories/playbook.md +1 -0
- package/registry/directories/prouct/directory.yaml +93 -0
- package/registry/directories/prouct/playbook.md +7 -0
- package/registry/directories/public-apis/directory.yaml +47 -0
- package/registry/directories/public-apis/playbook.md +6 -0
- package/registry/directories/publishyoursaas/directory.yaml +53 -0
- package/registry/directories/publishyoursaas/playbook.md +7 -0
- package/registry/directories/rankinpublic/directory.yaml +50 -0
- package/registry/directories/rankinpublic/playbook.md +1 -0
- package/registry/directories/rankyourai/directory.yaml +50 -0
- package/registry/directories/rankyourai/playbook.md +1 -0
- package/registry/directories/resource-fyi/directory.yaml +47 -0
- package/registry/directories/resource-fyi/playbook.md +5 -0
- package/registry/directories/roozna/directory.yaml +68 -0
- package/registry/directories/roozna/playbook.md +1 -0
- package/registry/directories/saas-fyi/directory.yaml +73 -0
- package/registry/directories/saas-fyi/playbook.md +7 -0
- package/registry/directories/saas-italia/directory.yaml +58 -0
- package/registry/directories/saas-launches/directory.yaml +47 -0
- package/registry/directories/saas-launches/playbook.md +6 -0
- package/registry/directories/saasbison/directory.yaml +44 -0
- package/registry/directories/saasbison/playbook.md +8 -0
- package/registry/directories/saascity/directory.yaml +50 -0
- package/registry/directories/saascubes/directory.yaml +49 -0
- package/registry/directories/saascubes/playbook.md +6 -0
- package/registry/directories/saasfame/directory.yaml +47 -0
- package/registry/directories/saasfame/playbook.md +6 -0
- package/registry/directories/saasgenius/directory.yaml +50 -0
- package/registry/directories/saasgenius/playbook.md +8 -0
- package/registry/directories/saasgrave-launches/directory.yaml +53 -0
- package/registry/directories/saasgrave-launches/playbook.md +7 -0
- package/registry/directories/saashammer/directory.yaml +53 -0
- package/registry/directories/saashammer/playbook.md +6 -0
- package/registry/directories/saashub/directory.yaml +51 -0
- package/registry/directories/saashub/playbook.md +9 -0
- package/registry/directories/saashunt/directory.yaml +43 -0
- package/registry/directories/saaslineup/directory.yaml +68 -0
- package/registry/directories/saaslineup/playbook.md +1 -0
- package/registry/directories/saaslist/directory.yaml +55 -0
- package/registry/directories/saaslist/playbook.md +5 -0
- package/registry/directories/saasmag-saas1000/directory.yaml +71 -0
- package/registry/directories/saaspage/directory.yaml +48 -0
- package/registry/directories/saasplace/directory.yaml +66 -0
- package/registry/directories/saasplace/playbook.md +1 -0
- package/registry/directories/saasrow/directory.yaml +51 -0
- package/registry/directories/saasrow/playbook.md +1 -0
- package/registry/directories/saassy-board/directory.yaml +54 -0
- package/registry/directories/saassy-board/playbook.md +1 -0
- package/registry/directories/saastore/directory.yaml +56 -0
- package/registry/directories/saastore/playbook.md +6 -0
- package/registry/directories/saastr-community-agents/directory.yaml +65 -0
- package/registry/directories/saastr-community-agents/playbook.md +8 -0
- package/registry/directories/saasvault/directory.yaml +48 -0
- package/registry/directories/saasvault/playbook.md +6 -0
- package/registry/directories/scrolllaunch/directory.yaml +54 -0
- package/registry/directories/scrolllaunch/playbook.md +8 -0
- package/registry/directories/seewhatnewai/directory.yaml +53 -0
- package/registry/directories/sellwithboost/directory.yaml +54 -0
- package/registry/directories/sellwithboost/playbook.md +5 -0
- package/registry/directories/serchen/directory.yaml +48 -0
- package/registry/directories/serchen/playbook.md +8 -0
- package/registry/directories/shinylaunch/directory.yaml +48 -0
- package/registry/directories/shinylaunch/playbook.md +6 -0
- package/registry/directories/shipyard-hq/directory.yaml +58 -0
- package/registry/directories/shipyard-hq/playbook.md +1 -0
- package/registry/directories/shipybara/directory.yaml +48 -0
- package/registry/directories/shipybara/playbook.md +8 -0
- package/registry/directories/shipycorn/directory.yaml +47 -0
- package/registry/directories/shipycorn/playbook.md +6 -0
- package/registry/directories/showmebest-ai/directory.yaml +50 -0
- package/registry/directories/showmebest-ai/playbook.md +1 -0
- package/registry/directories/showmysites/directory.yaml +52 -0
- package/registry/directories/showmysites/playbook.md +7 -0
- package/registry/directories/sidehunt/directory.yaml +56 -0
- package/registry/directories/sidehunt/playbook.md +1 -0
- package/registry/directories/sideprojectors/directory.yaml +55 -0
- package/registry/directories/sideprojectors/playbook.md +1 -0
- package/registry/directories/sideprojects/directory.yaml +52 -0
- package/registry/directories/sideprojects/playbook.md +1 -0
- package/registry/directories/sitepatent/directory.yaml +58 -0
- package/registry/directories/sitepatent/playbook.md +1 -0
- package/registry/directories/small-business-marketing-tools/directory.yaml +67 -0
- package/registry/directories/small-business-marketing-tools/playbook.md +1 -0
- package/registry/directories/smol-launch/directory.yaml +44 -0
- package/registry/directories/smol-launch/playbook.md +9 -0
- package/registry/directories/software-como-servicio/directory.yaml +64 -0
- package/registry/directories/software-como-servicio/playbook.md +6 -0
- package/registry/directories/software-finder/directory.yaml +47 -0
- package/registry/directories/software-finder/playbook.md +6 -0
- package/registry/directories/softwaresuggest/directory.yaml +68 -0
- package/registry/directories/softwaresuggest/playbook.md +6 -0
- package/registry/directories/solo-launches/directory.yaml +48 -0
- package/registry/directories/solo-launches/playbook.md +6 -0
- package/registry/directories/sololaunch/directory.yaml +69 -0
- package/registry/directories/sourceforge/directory.yaml +75 -0
- package/registry/directories/sourceforge/playbook.md +9 -0
- package/registry/directories/spacerrapps/directory.yaml +53 -0
- package/registry/directories/spacerrapps/playbook.md +6 -0
- package/registry/directories/speartip/directory.yaml +48 -0
- package/registry/directories/speartip/playbook.md +1 -0
- package/registry/directories/spiff-store/directory.yaml +46 -0
- package/registry/directories/spiff-store/playbook.md +6 -0
- package/registry/directories/spot-startups/directory.yaml +48 -0
- package/registry/directories/spotsaas/directory.yaml +45 -0
- package/registry/directories/squidir/directory.yaml +60 -0
- package/registry/directories/squidir/playbook.md +5 -0
- package/registry/directories/stackboo/directory.yaml +49 -0
- package/registry/directories/stackboo/playbook.md +6 -0
- package/registry/directories/stackledge/directory.yaml +84 -0
- package/registry/directories/stackledge/playbook.md +8 -0
- package/registry/directories/startup-alternatives/directory.yaml +68 -0
- package/registry/directories/startup-alternatives/playbook.md +5 -0
- package/registry/directories/startup-benchmarks/directory.yaml +59 -0
- package/registry/directories/startup-benchmarks/playbook.md +1 -0
- package/registry/directories/startup-collections/directory.yaml +56 -0
- package/registry/directories/startup-collections/playbook.md +5 -0
- package/registry/directories/startup-europe/directory.yaml +52 -0
- package/registry/directories/startup-europe/playbook.md +1 -0
- package/registry/directories/startup-fame/directory.yaml +51 -0
- package/registry/directories/startup-fame/playbook.md +8 -0
- package/registry/directories/startup-fast/directory.yaml +52 -0
- package/registry/directories/startup-fast/playbook.md +9 -0
- package/registry/directories/startup-list/directory.yaml +81 -0
- package/registry/directories/startup-list/playbook.md +8 -0
- package/registry/directories/startup-listing/directory.yaml +77 -0
- package/registry/directories/startup-listing/playbook.md +1 -0
- package/registry/directories/startup-og/directory.yaml +52 -0
- package/registry/directories/startup-ranking/directory.yaml +51 -0
- package/registry/directories/startup-ranking/playbook.md +1 -0
- package/registry/directories/startup-spotlight/directory.yaml +50 -0
- package/registry/directories/startup-spotlight/playbook.md +5 -0
- package/registry/directories/startup88/directory.yaml +46 -0
- package/registry/directories/startup88/playbook.md +1 -0
- package/registry/directories/startupaccelerator/directory.yaml +76 -0
- package/registry/directories/startupaccelerator/playbook.md +7 -0
- package/registry/directories/startupbase/directory.yaml +44 -0
- package/registry/directories/startupbase/playbook.md +1 -0
- package/registry/directories/startupbeats/directory.yaml +48 -0
- package/registry/directories/startupbeats/playbook.md +6 -0
- package/registry/directories/startupdirectory/directory.yaml +50 -0
- package/registry/directories/startupdirectory/playbook.md +1 -0
- package/registry/directories/startupinspire/directory.yaml +52 -0
- package/registry/directories/startupinspire/playbook.md +8 -0
- package/registry/directories/startuplibrary/directory.yaml +46 -0
- package/registry/directories/startuplibrary/playbook.md +1 -0
- package/registry/directories/startuplist-africa/directory.yaml +45 -0
- package/registry/directories/startuplist-africa/playbook.md +6 -0
- package/registry/directories/startups-gallery/directory.yaml +61 -0
- package/registry/directories/startups-gallery/playbook.md +5 -0
- package/registry/directories/startupsfm/directory.yaml +64 -0
- package/registry/directories/startupsfm/playbook.md +9 -0
- package/registry/directories/startupsfyi/directory.yaml +51 -0
- package/registry/directories/startupsfyi/playbook.md +4 -0
- package/registry/directories/startupslab/directory.yaml +61 -0
- package/registry/directories/startupslab/playbook.md +8 -0
- package/registry/directories/startuptile/directory.yaml +51 -0
- package/registry/directories/startuptile/playbook.md +5 -0
- package/registry/directories/startuptrusted/directory.yaml +46 -0
- package/registry/directories/startuptrusted/playbook.md +1 -0
- package/registry/directories/startuups/directory.yaml +50 -0
- package/registry/directories/steemhunt/directory.yaml +46 -0
- package/registry/directories/steemhunt/playbook.md +5 -0
- package/registry/directories/stellar-launch/directory.yaml +55 -0
- package/registry/directories/stellar-launch/playbook.md +1 -0
- package/registry/directories/stork-ai/directory.yaml +49 -0
- package/registry/directories/stork-ai/playbook.md +3 -0
- package/registry/directories/stork-mcp/directory.yaml +52 -0
- package/registry/directories/stork-mcp/playbook.md +3 -0
- package/registry/directories/submitaitools/directory.yaml +57 -0
- package/registry/directories/submitaitools/playbook.md +19 -0
- package/registry/directories/submithunt/directory.yaml +49 -0
- package/registry/directories/submithunt/playbook.md +9 -0
- package/registry/directories/submitmysaas/directory.yaml +51 -0
- package/registry/directories/submitmysaas/playbook.md +6 -0
- package/registry/directories/submitstartup/directory.yaml +73 -0
- package/registry/directories/sumodir/directory.yaml +47 -0
- package/registry/directories/sumodir/playbook.md +7 -0
- package/registry/directories/superlaunch/directory.yaml +64 -0
- package/registry/directories/superlaunch/playbook.md +7 -0
- package/registry/directories/techboard-australia/directory.yaml +54 -0
- package/registry/directories/techboard-australia/playbook.md +5 -0
- package/registry/directories/technologyadvice/directory.yaml +65 -0
- package/registry/directories/technologyadvice/playbook.md +8 -0
- package/registry/directories/tekpon/directory.yaml +44 -0
- package/registry/directories/tensorblock-mcp/directory.yaml +46 -0
- package/registry/directories/tensorblock-mcp/playbook.md +7 -0
- package/registry/directories/thankjohn/directory.yaml +42 -0
- package/registry/directories/thankjohn/playbook.md +1 -0
- package/registry/directories/that-ai-collection/directory.yaml +52 -0
- package/registry/directories/the-ai-library/directory.yaml +46 -0
- package/registry/directories/the-ai-library/playbook.md +1 -0
- package/registry/directories/the-next-ai/directory.yaml +62 -0
- package/registry/directories/the-popular-apps/directory.yaml +43 -0
- package/registry/directories/the-popular-apps/playbook.md +1 -0
- package/registry/directories/the-startup-inc/directory.yaml +90 -0
- package/registry/directories/the-startup-inc/playbook.md +4 -0
- package/registry/directories/the-startup-project/directory.yaml +67 -0
- package/registry/directories/the-startup-project/playbook.md +5 -0
- package/registry/directories/the-tool-directory/directory.yaml +75 -0
- package/registry/directories/thedevtoolsdir/directory.yaml +57 -0
- package/registry/directories/thedevtoolsdir/playbook.md +6 -0
- package/registry/directories/theres-an-ai/directory.yaml +44 -0
- package/registry/directories/theres-an-ai/playbook.md +1 -0
- package/registry/directories/theresanaiforthat/directory.yaml +51 -0
- package/registry/directories/theresanaiforthat/playbook.md +1 -0
- package/registry/directories/thesaasdir/directory.yaml +44 -0
- package/registry/directories/thesaasdir/playbook.md +6 -0
- package/registry/directories/tinylaunch/directory.yaml +50 -0
- package/registry/directories/tinylaunch/playbook.md +8 -0
- package/registry/directories/tinystartups/directory.yaml +61 -0
- package/registry/directories/tinystartups/playbook.md +6 -0
- package/registry/directories/tipseason/directory.yaml +68 -0
- package/registry/directories/tolodora/directory.yaml +59 -0
- package/registry/directories/tool-commons/directory.yaml +46 -0
- package/registry/directories/tool-commons/playbook.md +1 -0
- package/registry/directories/tool-directory-ai/directory.yaml +64 -0
- package/registry/directories/tool-directory-ai/playbook.md +5 -0
- package/registry/directories/tool-finder/directory.yaml +64 -0
- package/registry/directories/tool-finder/playbook.md +6 -0
- package/registry/directories/tooldirs/directory.yaml +64 -0
- package/registry/directories/tooldirs/playbook.md +7 -0
- package/registry/directories/toolfame/directory.yaml +47 -0
- package/registry/directories/toolfame/playbook.md +6 -0
- package/registry/directories/toolfio/directory.yaml +48 -0
- package/registry/directories/toolfio/playbook.md +7 -0
- package/registry/directories/toolfolio/directory.yaml +54 -0
- package/registry/directories/toolfolio/playbook.md +1 -0
- package/registry/directories/toolfound/directory.yaml +66 -0
- package/registry/directories/toolify/directory.yaml +52 -0
- package/registry/directories/toolify/playbook.md +8 -0
- package/registry/directories/toolkitly/directory.yaml +46 -0
- package/registry/directories/toolkitly/playbook.md +1 -0
- package/registry/directories/toollist-ai/directory.yaml +60 -0
- package/registry/directories/toollist-ai/playbook.md +1 -0
- package/registry/directories/toolomio/directory.yaml +55 -0
- package/registry/directories/toolomio/playbook.md +5 -0
- package/registry/directories/toolpasta/directory.yaml +46 -0
- package/registry/directories/toolpasta/playbook.md +1 -0
- package/registry/directories/toolpilot-ai/directory.yaml +77 -0
- package/registry/directories/toolpilot-ai/playbook.md +8 -0
- package/registry/directories/toolpromote/directory.yaml +81 -0
- package/registry/directories/toolrain/directory.yaml +48 -0
- package/registry/directories/toolrain/playbook.md +1 -0
- package/registry/directories/tools-ai-online/directory.yaml +65 -0
- package/registry/directories/tools-ai-online/playbook.md +7 -0
- package/registry/directories/tools-cafe/directory.yaml +51 -0
- package/registry/directories/tools-cafe/playbook.md +6 -0
- package/registry/directories/toolscout/directory.yaml +45 -0
- package/registry/directories/toolscout/playbook.md +5 -0
- package/registry/directories/toolsfine/directory.yaml +58 -0
- package/registry/directories/toolsfine/playbook.md +9 -0
- package/registry/directories/toolsnocode/directory.yaml +48 -0
- package/registry/directories/toolsnocode/playbook.md +6 -0
- package/registry/directories/tooltrove/directory.yaml +71 -0
- package/registry/directories/tooltrove/playbook.md +9 -0
- package/registry/directories/toolveyor/directory.yaml +44 -0
- package/registry/directories/toolveyor/playbook.md +1 -0
- package/registry/directories/toolwise/directory.yaml +67 -0
- package/registry/directories/toolwise/playbook.md +7 -0
- package/registry/directories/toools-design/directory.yaml +64 -0
- package/registry/directories/toools-design/playbook.md +8 -0
- package/registry/directories/topai-tools/directory.yaml +43 -0
- package/registry/directories/topai-tools/playbook.md +1 -0
- package/registry/directories/traffiklist/directory.yaml +55 -0
- package/registry/directories/traffiklist/playbook.md +6 -0
- package/registry/directories/trendy-startups/directory.yaml +49 -0
- package/registry/directories/trendy-startups/playbook.md +1 -0
- package/registry/directories/trillionagent/directory.yaml +57 -0
- package/registry/directories/trillionagent/playbook.md +1 -0
- package/registry/directories/truerev/directory.yaml +83 -0
- package/registry/directories/truerev/playbook.md +12 -0
- package/registry/directories/trustmrr/directory.yaml +43 -0
- package/registry/directories/trustradius/directory.yaml +67 -0
- package/registry/directories/trustradius/playbook.md +9 -0
- package/registry/directories/trylaunch/directory.yaml +51 -0
- package/registry/directories/trylaunch/playbook.md +1 -0
- package/registry/directories/tulimoa/directory.yaml +65 -0
- package/registry/directories/tulimoa/playbook.md +7 -0
- package/registry/directories/turbo0/directory.yaml +49 -0
- package/registry/directories/turbo0/playbook.md +9 -0
- package/registry/directories/twelve-tools/directory.yaml +47 -0
- package/registry/directories/twelve-tools/playbook.md +6 -0
- package/registry/directories/ultimate-free-tools/directory.yaml +49 -0
- package/registry/directories/ultimate-free-tools/playbook.md +8 -0
- package/registry/directories/uneed/directory.yaml +55 -0
- package/registry/directories/uneed/playbook.md +9 -0
- package/registry/directories/unitelist/directory.yaml +49 -0
- package/registry/directories/unitelist/playbook.md +7 -0
- package/registry/directories/uno-directory/directory.yaml +48 -0
- package/registry/directories/uno-directory/playbook.md +1 -0
- package/registry/directories/ventureradar/directory.yaml +47 -0
- package/registry/directories/ventureradar/playbook.md +5 -0
- package/registry/directories/vibe-coding-cloud/directory.yaml +45 -0
- package/registry/directories/vibe-coding-cloud/playbook.md +6 -0
- package/registry/directories/victrays/directory.yaml +55 -0
- package/registry/directories/victrays/playbook.md +8 -0
- package/registry/directories/visalytica/directory.yaml +59 -0
- package/registry/directories/wavel/directory.yaml +51 -0
- package/registry/directories/we-are-founders/directory.yaml +85 -0
- package/registry/directories/we-are-founders/playbook.md +1 -0
- package/registry/directories/web-store-extensions/directory.yaml +45 -0
- package/registry/directories/web-store-extensions/playbook.md +6 -0
- package/registry/directories/webcurate/directory.yaml +50 -0
- package/registry/directories/website-rating/directory.yaml +66 -0
- package/registry/directories/website-rating/playbook.md +9 -0
- package/registry/directories/webspot/directory.yaml +46 -0
- package/registry/directories/webspot/playbook.md +5 -0
- package/registry/directories/weekhack/directory.yaml +56 -0
- package/registry/directories/weekhack/playbook.md +5 -0
- package/registry/directories/weliketools/directory.yaml +50 -0
- package/registry/directories/weliketools/playbook.md +6 -0
- package/registry/directories/what-is-ai-tools/directory.yaml +48 -0
- package/registry/directories/what-is-ai-tools/playbook.md +1 -0
- package/registry/directories/wired-business/directory.yaml +44 -0
- package/registry/directories/wired-business/playbook.md +6 -0
- package/registry/directories/would-you-pay/directory.yaml +49 -0
- package/registry/directories/would-you-pay/playbook.md +6 -0
- package/registry/directories/xai-plugin-marketplace/directory.yaml +46 -0
- package/registry/directories/xai-plugin-marketplace/playbook.md +5 -0
- package/registry/directories/zplatform-ai/directory.yaml +75 -0
- package/registry/directories/zumvu/directory.yaml +50 -0
- package/scripts/ab +40 -0
- package/scripts/check-listings +8 -0
- package/scripts/google-login +13 -0
- package/scripts/show-browser +17 -0
- package/src/accounts.ts +81 -0
- package/src/audit.ts +229 -0
- package/src/browser.ts +123 -0
- package/src/capabilities.ts +62 -0
- package/src/cli.ts +741 -0
- package/src/db/index.ts +29 -0
- package/src/db/schema.ts +152 -0
- package/src/directory-kinds.ts +12 -0
- package/src/import-hosted.ts +156 -0
- package/src/jobs.ts +270 -0
- package/src/listings.ts +64 -0
- package/src/manual.ts +26 -0
- package/src/product-approvals.ts +31 -0
- package/src/public.ts +24 -0
- package/src/ratings.ts +71 -0
- package/src/registry-updates.ts +54 -0
- package/src/registry.ts +101 -0
- package/src/review.ts +58 -0
- package/src/server.ts +186 -0
- package/src/views.ts +71 -0
- package/src/web/app.tsx +1026 -0
- package/src/web/favicon.svg +4 -0
- package/src/web/index.html +14 -0
- package/src/web/style.css +301 -0
- package/src/workspace.ts +97 -0
package/src/audit.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Measures what a listing is actually worth, without AI: the rel attribute on outbound product links of real
|
|
3
|
+
* listing pages, whether those pages are indexable, and which bot check guards the submit form.
|
|
4
|
+
* Every verdict carries the page it was measured on.
|
|
5
|
+
*/
|
|
6
|
+
import { locs } from "./listings";
|
|
7
|
+
import { normalizeUrl, type Directory } from "./registry";
|
|
8
|
+
|
|
9
|
+
const UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130 Safari/537.36";
|
|
10
|
+
export type Rel = "dofollow" | "nofollow" | "ugc" | "sponsored" | "mixed" | "unknown";
|
|
11
|
+
export type LinkAudit = {
|
|
12
|
+
rel: Rel;
|
|
13
|
+
method: "own_listing" | "sample_listing" | "platform_rule" | "none";
|
|
14
|
+
evidence: string | null; // the listing page the rel was read on
|
|
15
|
+
samples: number; // listing pages that had outbound product links
|
|
16
|
+
noindex: boolean | null; // listing page tells search engines not to index it
|
|
17
|
+
claimed: "dofollow" | "nofollow" | null; // what the directory's own text says
|
|
18
|
+
bot_check: "recaptcha" | "turnstile" | "hcaptcha" | "math" | "none" | "unknown";
|
|
19
|
+
checked_at: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
async function get(url: string, timeout = 15000) {
|
|
23
|
+
try {
|
|
24
|
+
const r = await fetch(url, { redirect: "follow", headers: { "user-agent": UA, accept: "text/html,application/xhtml+xml,application/xml" }, signal: AbortSignal.timeout(timeout) });
|
|
25
|
+
const ct = r.headers.get("content-type") ?? "";
|
|
26
|
+
const body = r.ok && /html|xml|text/.test(ct) ? (await r.text()).slice(0, 2_000_000) : (await r.body?.cancel(), "");
|
|
27
|
+
return { status: r.status, url: r.url, body, robots: r.headers.get("x-robots-tag") ?? "" };
|
|
28
|
+
} catch {
|
|
29
|
+
return { status: 0, url, body: "", robots: "" };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Hosts that are never "the product's website" on a listing page.
|
|
34
|
+
const NOISE = /(^|\.)(twitter|x|facebook|linkedin|instagram|youtube|youtu|tiktok|pinterest|reddit|discord|t|telegram|medium|google|googletagmanager|googleapis|gstatic|doubleclick|apple|microsoft|cloudflare|gravatar|wp|wordpress|w3|schema|ahrefs|semrush|producthunt|github|gitlab|vercel|netlify|stripe|paypal|buymeacoffee|ko-fi|patreon|substack|beehiiv|mailchimp|typeform|tally|notion|calendly|cal|crisp|intercom|hotjar|plausible|umami|fonts|jsdelivr|unpkg|cdnjs)\.[a-z.]+$/;
|
|
35
|
+
|
|
36
|
+
/** rel of each outbound link on a page, keyed by host; nofollow wins over ugc/sponsored when combined. */
|
|
37
|
+
export function outboundRels(html: string, pageHost: string) {
|
|
38
|
+
const out: { host: string; href: string; rel: Exclude<Rel, "mixed" | "unknown">; visit: boolean }[] = [];
|
|
39
|
+
for (const m of html.matchAll(/<a\b([^>]*)>([\s\S]{0,600}?)<\/a>/gi)) {
|
|
40
|
+
const attrs = m[1]!;
|
|
41
|
+
const label = m[2]!.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
42
|
+
const href = /\bhref\s*=\s*["']([^"']+)["']/i.exec(attrs)?.[1];
|
|
43
|
+
if (!href || !/^https?:\/\//i.test(href)) continue;
|
|
44
|
+
let host: string;
|
|
45
|
+
try { host = new URL(href).hostname.toLowerCase().replace(/^www\./, ""); } catch { continue; }
|
|
46
|
+
if (host === pageHost || host.endsWith(`.${pageHost}`) || pageHost.endsWith(`.${host}`) || NOISE.test(host)) continue;
|
|
47
|
+
const rel = (/\brel\s*=\s*["']([^"']*)["']/i.exec(attrs)?.[1] ?? "").toLowerCase();
|
|
48
|
+
// The listing's "go to the product" link: visit/website wording, or a ref/utm parameter naming the directory.
|
|
49
|
+
const visit = /\b(visit|website|try|get started|go to|open|homepage|view site|official)\b/i.test(label) || /visit|website|outbound|external-link/i.test(attrs) || /[?&](ref|utm_source|via|source)=/i.test(href);
|
|
50
|
+
out.push({ host, href, visit, rel: /nofollow/.test(rel) ? "nofollow" : /sponsored/.test(rel) ? "sponsored" : /ugc/.test(rel) ? "ugc" : "dofollow" });
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const isNoindex = (html: string, header: string) =>
|
|
56
|
+
/noindex/i.test(header) || /<meta[^>]+name=["'](robots|googlebot)["'][^>]+content=["'][^"']*noindex/i.test(html) || /<meta[^>]+content=["'][^"']*noindex[^"']*["'][^>]+name=["'](robots|googlebot)["']/i.test(html);
|
|
57
|
+
|
|
58
|
+
export function botCheck(html: string): LinkAudit["bot_check"] {
|
|
59
|
+
if (!html) return "unknown";
|
|
60
|
+
if (/challenges\.cloudflare\.com\/turnstile|cf-turnstile/i.test(html)) return "turnstile";
|
|
61
|
+
if (/hcaptcha\.com|h-captcha/i.test(html)) return "hcaptcha";
|
|
62
|
+
if (/google\.com\/recaptcha|recaptcha\/api|g-recaptcha|grecaptcha/i.test(html)) return "recaptcha";
|
|
63
|
+
if (/what is \d+\s*[+\-x×*]\s*\d+|anti-?spam (question|check)|solve.{0,20}(sum|math)/i.test(html)) return "math";
|
|
64
|
+
return "none";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** What the directory says about its links, from the registry's own notes and conditions. */
|
|
68
|
+
export function claimedRel(text: string): LinkAudit["claimed"] {
|
|
69
|
+
const t = text.toLowerCase();
|
|
70
|
+
const no = /no-?follow/.test(t), dof = /do-?follow/.test(t);
|
|
71
|
+
if (dof && !no) return "dofollow";
|
|
72
|
+
if (no && !dof) return "nofollow";
|
|
73
|
+
if (dof && no) return /free[^.]{0,60}no-?follow|no-?follow[^.]{0,40}free/.test(t) ? "nofollow" : "dofollow";
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Paths that look like a single product's page on a directory.
|
|
78
|
+
const PRODUCT_PATH = /\/(tools?|products?|p|startups?|listings?|apps?|software|items?|launch(es)?|projects?|ai|saas|companies|company|directory|site|sites|s)\/[^/?#]{2,}\/?$/i;
|
|
79
|
+
|
|
80
|
+
async function sampleListings(site: string, max = 4) {
|
|
81
|
+
const origin = new URL(site).origin;
|
|
82
|
+
const robots = (await get(`${origin}/robots.txt`, 8000)).body;
|
|
83
|
+
const queue = [...robots.matchAll(/^sitemap:\s*(\S+)/gim)].map((m) => m[1]!);
|
|
84
|
+
if (!queue.length) queue.push(`${origin}/sitemap.xml`);
|
|
85
|
+
const found: string[] = [];
|
|
86
|
+
for (let i = 0; i < queue.length && i < 6 && found.length < 40; i++) {
|
|
87
|
+
const xml = (await get(queue[i]!, 12000)).body;
|
|
88
|
+
const urls = locs(xml);
|
|
89
|
+
if (/<sitemapindex/i.test(xml)) {
|
|
90
|
+
// Prefer child sitemaps that sound like products.
|
|
91
|
+
queue.push(...urls.sort((a, b) => Number(/tool|product|startup|listing|app|item|launch|project/i.test(b)) - Number(/tool|product|startup|listing|app|item|launch|project/i.test(a))));
|
|
92
|
+
} else found.push(...urls.filter((u) => PRODUCT_PATH.test(new URL(u, origin).pathname)));
|
|
93
|
+
}
|
|
94
|
+
// Spread the picks across the list: newest and oldest listings can differ.
|
|
95
|
+
const step = Math.max(1, Math.floor(found.length / max));
|
|
96
|
+
return [...new Set(found.filter((_, i) => i % step === 0))].slice(0, max);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const tally = (rels: Rel[]): Rel => {
|
|
100
|
+
const kinds = new Set(rels);
|
|
101
|
+
if (!kinds.size) return "unknown";
|
|
102
|
+
if (kinds.size === 1) return rels[0]!;
|
|
103
|
+
// A page with both is usually "dofollow for paid/featured, nofollow for the rest": call it mixed.
|
|
104
|
+
return "mixed";
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export async function auditDirectory(d: Directory, ownListings: { url: string; productHost: string }[], playbook = ""): Promise<LinkAudit> {
|
|
108
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
109
|
+
const site = normalizeUrl(d.website).domain;
|
|
110
|
+
const claimed = claimedRel([d.cost.notes ?? "", ...d.submission.conditions, playbook].join(" "));
|
|
111
|
+
const submit = d.submission_url ? await get(d.submission_url) : null;
|
|
112
|
+
const base = { claimed, bot_check: submit ? botCheck(submit.body) : ("unknown" as const), checked_at: today };
|
|
113
|
+
|
|
114
|
+
// GitHub renders every user-content link with rel="nofollow".
|
|
115
|
+
if (/^github\.com$|\.github\.io$/.test(site) && /github\.com/.test(d.website))
|
|
116
|
+
return { ...base, rel: "nofollow", method: "platform_rule", evidence: d.website, samples: 0, noindex: false };
|
|
117
|
+
|
|
118
|
+
// 1. Our own live listings: the exact link we got.
|
|
119
|
+
for (const own of ownListings) {
|
|
120
|
+
const page = await get(own.url);
|
|
121
|
+
const links = outboundRels(page.body, site).filter((l) => l.host === own.productHost || l.host.endsWith(`.${own.productHost}`));
|
|
122
|
+
// The URL stays on the submission: the shared registry never names a product.
|
|
123
|
+
if (links.length) return { ...base, rel: tally(links.map((l) => l.rel)), method: "own_listing", evidence: null, samples: 1, noindex: isNoindex(page.body, page.robots) };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 2. Other products' listing pages from the sitemap: the dominant rel on outbound product links.
|
|
127
|
+
const rels: Rel[] = [];
|
|
128
|
+
let evidence: string | null = null, noindex: boolean | null = null, samples = 0;
|
|
129
|
+
for (const url of await sampleListings(d.website)) {
|
|
130
|
+
const page = await get(url);
|
|
131
|
+
// Only pages with a visit-style link to one external product count as listings.
|
|
132
|
+
const links = outboundRels(page.body, site).filter((l) => l.visit);
|
|
133
|
+
const hosts = new Set(links.map((l) => l.host));
|
|
134
|
+
if (!links.length || hosts.size > 3) continue; // none, or a page of many products (a category page)
|
|
135
|
+
const counts = new Map<string, number>();
|
|
136
|
+
for (const l of links) counts.set(l.host, (counts.get(l.host) ?? 0) + 1);
|
|
137
|
+
const top = [...counts].sort((a, b) => b[1] - a[1])[0]![0];
|
|
138
|
+
rels.push(tally(links.filter((l) => l.host === top).map((l) => l.rel)));
|
|
139
|
+
evidence ??= url;
|
|
140
|
+
noindex ??= isNoindex(page.body, page.robots);
|
|
141
|
+
samples++;
|
|
142
|
+
}
|
|
143
|
+
return { ...base, rel: tally(rels), method: samples ? "sample_listing" : "none", evidence, samples, noindex };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const NOT_LISTING = /^(c|cat|category|categories|tag|tags|topics?|collections?|directories|blog|posts?|news|articles?|guides?|alternatives?|compare|vs|best|top|page|search|login|log-in|signin|sign-in|signup|sign-up|register|account|dashboard|pricing|plans|about|contact|submit|add|faq|help|docs|terms|privacy|legal|cookies?|sitemap|rss|feed|advertise|sponsor|newsletter|api|users?|u|profile|makers?|authors?|en|de|fr|es|it)$/i;
|
|
147
|
+
|
|
148
|
+
// ---- browser pass: real Chrome via agent-browser, for pages a plain fetch can't read -------------------------
|
|
149
|
+
|
|
150
|
+
type Anchor = { href: string; rel: string; text: string; cls: string };
|
|
151
|
+
type Rendered = { finalUrl: string; anchors: Anchor[]; robots: string; challenge: boolean };
|
|
152
|
+
|
|
153
|
+
/** Renders a page in a throwaway, logged-out browser session and returns its links. Read-only. */
|
|
154
|
+
export function renderAnchors(url: string, session: string): Rendered | null {
|
|
155
|
+
const ab = (...args: string[]) => Bun.spawnSync(["agent-browser", "--session", session, ...args], { stderr: "pipe", timeout: 60_000 });
|
|
156
|
+
if (ab("open", url).exitCode) return null;
|
|
157
|
+
ab("wait", "2500");
|
|
158
|
+
const script = `JSON.stringify({ finalUrl: location.href,
|
|
159
|
+
robots: (document.querySelector('meta[name=robots]') || {}).content || "",
|
|
160
|
+
challenge: /just a moment|checking your browser|verify you are human|ci siamo quasi/i.test(document.title + " " + document.body.innerText.slice(0, 400)),
|
|
161
|
+
anchors: [...document.querySelectorAll("a[href]")].slice(0, 600).map(a => ({ href: a.href, rel: a.rel || "", text: (a.innerText || a.getAttribute("aria-label") || "").trim().slice(0, 60), cls: String(a.className || "").slice(0, 120) })) })`;
|
|
162
|
+
const out = ab("eval", script);
|
|
163
|
+
try {
|
|
164
|
+
let v = JSON.parse(out.stdout.toString());
|
|
165
|
+
if (typeof v === "string") v = JSON.parse(v);
|
|
166
|
+
return v as Rendered;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const relOfAttr = (rel: string): Exclude<Rel, "mixed" | "unknown"> =>
|
|
173
|
+
/nofollow/i.test(rel) ? "nofollow" : /sponsored/i.test(rel) ? "sponsored" : /ugc/i.test(rel) ? "ugc" : "dofollow";
|
|
174
|
+
const hostOf = (u: string) => { try { return new URL(u).hostname.toLowerCase().replace(/^www\./, ""); } catch { return ""; } };
|
|
175
|
+
|
|
176
|
+
/** Same rules as the fetch audit, applied to rendered pages: listing links from the homepage, visit links on each. */
|
|
177
|
+
export function auditDirectoryBrowser(d: Directory, session: string): { link: LinkAudit; note: string } {
|
|
178
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
179
|
+
const site = normalizeUrl(d.website).domain;
|
|
180
|
+
const base = { claimed: d.link?.claimed ?? null, bot_check: d.link?.bot_check ?? ("unknown" as const), checked_at: today };
|
|
181
|
+
const home = renderAnchors(d.website, session);
|
|
182
|
+
if (!home) return { link: { ...base, rel: "unknown", method: "none", evidence: null, samples: 0, noindex: null }, note: "homepage did not load" };
|
|
183
|
+
if (home.challenge) return { link: { ...base, rel: "unknown", method: "none", evidence: null, samples: 0, noindex: null }, note: "bot challenge on the homepage" };
|
|
184
|
+
// Listing pages are the biggest family of internal links sharing a path pattern. Which family is the listings
|
|
185
|
+
// (vs categories or tags) is tested, not guessed: the right one has a single "visit" link per page.
|
|
186
|
+
const groups = new Map<string, string[]>();
|
|
187
|
+
for (const h of new Set(home.anchors.map((a) => a.href.split(/[?#]/)[0]!))) {
|
|
188
|
+
const host = hostOf(h);
|
|
189
|
+
if (!(host === site || host.endsWith(`.${site}`))) continue;
|
|
190
|
+
const parts = new URL(h).pathname.split("/").filter(Boolean);
|
|
191
|
+
if (!parts.length || parts.some((p) => NOT_LISTING.test(p))) continue;
|
|
192
|
+
const key = `/${parts.slice(0, -1).join("/")}${parts.length > 1 ? "/" : ""}*`;
|
|
193
|
+
groups.set(key, [...(groups.get(key) ?? []), h]);
|
|
194
|
+
}
|
|
195
|
+
const families = [...groups].filter(([, urls]) => urls.length >= 5).sort((a, b) => b[1].length - a[1].length).slice(0, 3);
|
|
196
|
+
const listings = families.flatMap(([, urls]) => urls);
|
|
197
|
+
const rels: Rel[] = [];
|
|
198
|
+
let evidence: string | null = null, noindex: boolean | null = null, samples = 0;
|
|
199
|
+
const read = (url: string) => {
|
|
200
|
+
const page = renderAnchors(url, session);
|
|
201
|
+
if (!page || page.challenge) return false;
|
|
202
|
+
const outbound = page.anchors.filter((a) => {
|
|
203
|
+
const host = hostOf(a.href);
|
|
204
|
+
return !!host && host !== site && !host.endsWith(`.${site}`) && !site.endsWith(`.${host}`) && !NOISE.test(host);
|
|
205
|
+
});
|
|
206
|
+
// A button that says Visit/Website is the listing's own link; ref-tagged links alone may be sidebar promos.
|
|
207
|
+
const worded = outbound.filter((a) => /\b(visit|website|try|get started|go to|open|homepage|view site|official)\b/i.test(a.text) || /visit|website|outbound|external-link/i.test(a.cls));
|
|
208
|
+
const links = worded.length ? worded : outbound.filter((a) => /[?&](ref|utm_source|via|source)=/i.test(a.href));
|
|
209
|
+
const hosts = new Set(links.map((l) => hostOf(l.href)));
|
|
210
|
+
if (!links.length || hosts.size > 3) return false;
|
|
211
|
+
const counts = new Map<string, number>();
|
|
212
|
+
for (const l of links) counts.set(hostOf(l.href), (counts.get(hostOf(l.href)) ?? 0) + 1);
|
|
213
|
+
const top = [...counts].sort((x, y) => y[1] - x[1])[0]![0];
|
|
214
|
+
rels.push(tally(links.filter((l) => hostOf(l.href) === top).map((l) => relOfAttr(l.rel))));
|
|
215
|
+
evidence ??= page.finalUrl;
|
|
216
|
+
noindex ??= /noindex/i.test(page.robots);
|
|
217
|
+
samples++;
|
|
218
|
+
return true;
|
|
219
|
+
};
|
|
220
|
+
for (const [, urls] of families) {
|
|
221
|
+
const step = Math.max(1, Math.floor(urls.length / 4));
|
|
222
|
+
const spread = urls.filter((_, i) => i % step === 0);
|
|
223
|
+
if (!spread.slice(0, 2).map(read).some(Boolean)) continue; // not this family
|
|
224
|
+
for (const url of spread.slice(2, 4)) read(url);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
const note = !listings.length ? "no listing-like link families on the homepage" : samples ? `${samples} listing page${samples === 1 ? "" : "s"} read` : `tried ${families.length} link famil${families.length === 1 ? "y" : "ies"}, no page with a single visit link`;
|
|
228
|
+
return { link: { ...base, rel: tally(rels), method: samples ? "sample_listing" : "none", evidence, samples, noindex }, note };
|
|
229
|
+
}
|
package/src/browser.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The agents' browser: a dedicated, real Google Chrome profile (data/browser-profile) with remote debugging on
|
|
3
|
+
* a local port. Any agent that can run shell commands drives it with agent-browser (`scripts/ab` attaches to it);
|
|
4
|
+
* the CLI starts it and handles secrets, so passwords never pass through a model.
|
|
5
|
+
*
|
|
6
|
+
* Google services other than sign-in are unreachable from this profile: a PAC script sends them to a dead proxy.
|
|
7
|
+
* That holds for every client attached over CDP, not only for one wrapper.
|
|
8
|
+
*/
|
|
9
|
+
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { HOME } from "./db";
|
|
13
|
+
|
|
14
|
+
export const BROWSER_PORT = Number(process.env.LAUNCH_BROWSER_PORT ?? 9222);
|
|
15
|
+
export const PROFILE = join(HOME, "data/browser-profile");
|
|
16
|
+
const CHROME = process.env.LAUNCH_CHROME ?? "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
17
|
+
const BASE = `http://127.0.0.1:${BROWSER_PORT}`;
|
|
18
|
+
/** One agent-browser session for secret handling, attached to the same Chrome. */
|
|
19
|
+
const SECRETS_SESSION = "dirpush-secrets";
|
|
20
|
+
|
|
21
|
+
/** Same list as scripts/ab: the profile is signed into the human's Google account; only sign-in pages may load. */
|
|
22
|
+
export const BLOCKED_HOSTS = [
|
|
23
|
+
"mail.google.com", "gmail.com", "www.gmail.com", "inbox.google.com",
|
|
24
|
+
"drive.google.com", "drive.usercontent.google.com", "docs.google.com", "sheets.google.com", "slides.google.com", "forms.google.com",
|
|
25
|
+
"photos.google.com", "keep.google.com", "calendar.google.com", "contacts.google.com",
|
|
26
|
+
"myaccount.google.com", "security.google.com", "myactivity.google.com", "passwords.google.com",
|
|
27
|
+
"pay.google.com", "payments.google.com", "wallet.google.com", "takeout.google.com", "admin.google.com",
|
|
28
|
+
"script.google.com", "console.cloud.google.com", "cloud.google.com", "console.developers.google.com",
|
|
29
|
+
"chromewebstore.google.com", "chrome.google.com",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function pacScript(hosts = BLOCKED_HOSTS) {
|
|
33
|
+
return `function FindProxyForURL(url, host) { var b = ${JSON.stringify(hosts)}; for (var i = 0; i < b.length; i++) if (host === b[i]) return "PROXY 127.0.0.1:9"; return "DIRECT"; }`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type Target = { id: string; type: string; url: string; title: string; webSocketDebuggerUrl?: string };
|
|
37
|
+
const json = async <T>(path: string) => (await fetch(`${BASE}${path}`, { signal: AbortSignal.timeout(1500) })).json() as Promise<T>;
|
|
38
|
+
|
|
39
|
+
export async function browserStatus() {
|
|
40
|
+
try {
|
|
41
|
+
const v = await json<{ Browser: string }>("/json/version");
|
|
42
|
+
const pages = (await json<Target[]>("/json/list")).filter((t) => t.type === "page");
|
|
43
|
+
return { running: true, port: BROWSER_PORT, browserUrl: BASE, chrome: v.Browser, pages: pages.map((p) => ({ title: p.title.slice(0, 80), url: p.url.slice(0, 200) })) };
|
|
44
|
+
} catch {
|
|
45
|
+
return { running: false, port: BROWSER_PORT, browserUrl: BASE };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function startBrowser() {
|
|
50
|
+
const now = await browserStatus();
|
|
51
|
+
if (now.running) return { ...now, started: false };
|
|
52
|
+
if (!existsSync(CHROME)) throw new Error(`Google Chrome not found at ${CHROME} (set LAUNCH_CHROME)`);
|
|
53
|
+
const pac = `data:application/x-ns-proxy-autoconfig;base64,${Buffer.from(pacScript()).toString("base64")}`;
|
|
54
|
+
// Plain Chrome, headed, no automation flags: Google sign-in and bot checks see a normal browser. The keychain
|
|
55
|
+
// flags match agent-browser and scripts/google-login: the profile's cookies are encrypted with that key, so
|
|
56
|
+
// without them every saved session would be unreadable.
|
|
57
|
+
Bun.spawn([CHROME, `--user-data-dir=${PROFILE}`, `--remote-debugging-port=${BROWSER_PORT}`, `--proxy-pac-url=${pac}`, "--use-mock-keychain", "--password-store=basic", "--no-first-run", "--no-default-browser-check"], {
|
|
58
|
+
stdout: "ignore", stderr: "ignore", stdin: "ignore",
|
|
59
|
+
}).unref();
|
|
60
|
+
for (let i = 0; i < 40; i++) {
|
|
61
|
+
await Bun.sleep(250);
|
|
62
|
+
const s = await browserStatus();
|
|
63
|
+
if (s.running) return { ...s, started: true };
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`Chrome did not open port ${BROWSER_PORT}. Is this profile already open without remote debugging? Quit that window and retry.`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function stopBrowser() {
|
|
69
|
+
const s = await browserStatus();
|
|
70
|
+
if (!s.running) return { stopped: false, running: false };
|
|
71
|
+
const { webSocketDebuggerUrl } = await json<{ webSocketDebuggerUrl: string }>("/json/version");
|
|
72
|
+
await new Promise<void>((resolve) => {
|
|
73
|
+
const ws = new WebSocket(webSocketDebuggerUrl);
|
|
74
|
+
ws.onopen = () => ws.send(JSON.stringify({ id: 1, method: "Browser.close" }));
|
|
75
|
+
ws.onclose = ws.onerror = () => resolve();
|
|
76
|
+
setTimeout(resolve, 3000);
|
|
77
|
+
});
|
|
78
|
+
return { stopped: true };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function ab(args: string[], stdin?: string) {
|
|
82
|
+
const p = Bun.spawnSync(["agent-browser", "--session", SECRETS_SESSION, "--cdp", String(BROWSER_PORT), ...args], { stdin: stdin === undefined ? "ignore" : Buffer.from(stdin), stdout: "pipe", stderr: "pipe", timeout: 60_000 });
|
|
83
|
+
const out = p.stdout.toString().trim();
|
|
84
|
+
if (p.exitCode !== 0) throw new Error(`agent-browser ${args[0]} failed: ${(p.stderr.toString() || out).trim().slice(0, 300)}`);
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The open tab on `site`'s host (the one the agent is working in); the most recently active one if several. */
|
|
89
|
+
async function pageOn(site: string) {
|
|
90
|
+
const host = new URL(site.includes("://") ? site : `https://${site}`).hostname.replace(/^www\./, "");
|
|
91
|
+
if (!(await browserStatus()).running) throw new Error("the browser is not running: launch browser start");
|
|
92
|
+
const page = (await json<Target[]>("/json/list")).find((t) => t.type === "page" && /^https?:/.test(t.url) && new URL(t.url).hostname.replace(/^www\./, "").endsWith(host));
|
|
93
|
+
if (!page) throw new Error(`no open tab on ${host}; open the login/sign-up page first`);
|
|
94
|
+
ab(["tab", page.id]);
|
|
95
|
+
return page;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Fills a saved login (agent-browser vault) into the open tab on that site. The password never leaves the vault. */
|
|
99
|
+
export async function vaultLogin(name: string) {
|
|
100
|
+
const meta = JSON.parse(ab(["auth", "show", name, "--json"])) as { data?: { profile?: { url?: string } } };
|
|
101
|
+
const url = meta.data?.profile?.url;
|
|
102
|
+
if (!url) throw new Error(`vault entry ${name} has no login URL`);
|
|
103
|
+
const page = await pageOn(url);
|
|
104
|
+
ab(["auth", "login", name]);
|
|
105
|
+
return { vault: name, page: page.url.slice(0, 200), filled: true };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* New account password: generated here, typed into every given field of the open tab on `url`'s site, checked,
|
|
110
|
+
* then saved to the vault as `name`. Returns no secret.
|
|
111
|
+
*/
|
|
112
|
+
export async function newPassword(name: string, url: string, username: string, selectors: string[]) {
|
|
113
|
+
if (!selectors.length) throw new Error("pass the password field selector(s), e.g. 'input[type=password]'");
|
|
114
|
+
const page = await pageOn(url);
|
|
115
|
+
const pw = randomBytes(18).toString("base64");
|
|
116
|
+
for (const s of selectors) ab(["fill", s, pw]);
|
|
117
|
+
// A fill can "succeed" on a selector that matched nothing; never save a password the site didn't get.
|
|
118
|
+
const lengths = JSON.parse(ab(["eval", `JSON.stringify(${JSON.stringify(selectors)}.map((s) => document.querySelector(s)?.value.length ?? -1))`]));
|
|
119
|
+
const got = (typeof lengths === "string" ? JSON.parse(lengths) : lengths) as number[];
|
|
120
|
+
if (got.some((n) => n !== pw.length)) throw new Error(`password fields not filled (lengths ${got.join(", ")}); nothing saved`);
|
|
121
|
+
ab(["auth", "save", name, "--url", url, "--username", username, "--password-stdin"], pw);
|
|
122
|
+
return { vault: name, username, fields: selectors.length, page: page.url.slice(0, 200), saved: true };
|
|
123
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the agent working this workspace can actually use: a browser driver, an email tool for verification
|
|
3
|
+
* links and codes, anything else. The CLI detects what it can see (binaries on PATH); the agent records the rest
|
|
4
|
+
* (its own MCP/connector tools) during setup, because only the agent knows which tools its harness gives it.
|
|
5
|
+
* Stored in workspace/capabilities.yaml; no secrets.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { WORKSPACE } from "./workspace";
|
|
11
|
+
|
|
12
|
+
export const Capabilities = z.object({
|
|
13
|
+
checked_at: z.string().optional(),
|
|
14
|
+
checked_by: z.string().optional(), // agent/harness that recorded it, e.g. "claude-code"
|
|
15
|
+
browser: z
|
|
16
|
+
.object({
|
|
17
|
+
tool: z.string().default("agent-browser"),
|
|
18
|
+
notes: z.string().optional(),
|
|
19
|
+
})
|
|
20
|
+
.default({ tool: "agent-browser" }),
|
|
21
|
+
email: z
|
|
22
|
+
.object({
|
|
23
|
+
available: z.boolean(),
|
|
24
|
+
// How to reach it, in the agent's own words: "Executor Gmail app (tools.gmail.search, readMessage)",
|
|
25
|
+
// "claude.ai Gmail connector", "IMAP via himalaya CLI"…
|
|
26
|
+
tool: z.string().optional(),
|
|
27
|
+
address: z.string().optional(), // the inbox it reads; should match identity.yaml email
|
|
28
|
+
read_only: z.boolean().optional(), // true when the tool cannot send/delete/forward
|
|
29
|
+
notes: z.string().optional(),
|
|
30
|
+
})
|
|
31
|
+
.default({ available: false }),
|
|
32
|
+
notes: z.string().optional(),
|
|
33
|
+
});
|
|
34
|
+
export type Capabilities = z.infer<typeof Capabilities>;
|
|
35
|
+
|
|
36
|
+
export const CAPABILITIES = join(WORKSPACE, "capabilities.yaml");
|
|
37
|
+
|
|
38
|
+
export async function loadCapabilities(): Promise<Capabilities | null> {
|
|
39
|
+
if (!existsSync(CAPABILITIES)) return null;
|
|
40
|
+
return Capabilities.parse(Bun.YAML.parse(await Bun.file(CAPABILITIES).text()) ?? {});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Shallow-merges per section, so `set --input '{"email":{...}}'` leaves the browser entry alone. */
|
|
44
|
+
export async function saveCapabilities(patch: Record<string, unknown>, by?: string) {
|
|
45
|
+
const prev = (await loadCapabilities()) ?? Capabilities.parse({});
|
|
46
|
+
const merged: Record<string, unknown> = { ...prev };
|
|
47
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
48
|
+
const old = (prev as Record<string, unknown>)[k];
|
|
49
|
+
merged[k] = v && typeof v === "object" && !Array.isArray(v) && old && typeof old === "object" ? { ...old, ...v } : v;
|
|
50
|
+
}
|
|
51
|
+
const next = Capabilities.parse({ ...merged, checked_at: new Date().toISOString().slice(0, 10), ...(by && { checked_by: by }) });
|
|
52
|
+
await Bun.write(CAPABILITIES, Bun.YAML.stringify(next, null, 2));
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** What the CLI can see without the agent: browser driver binaries on PATH. */
|
|
57
|
+
export function detect() {
|
|
58
|
+
const which = (bin: string) => Bun.which(bin);
|
|
59
|
+
const ab = which("agent-browser");
|
|
60
|
+
const version = ab ? Bun.spawnSync([ab, "--version"], { stdout: "pipe", stderr: "ignore" }).stdout.toString().trim() : null;
|
|
61
|
+
return { agentBrowser: ab ? { path: ab, version } : null };
|
|
62
|
+
}
|