diff --git a/src/Composer/Repository/ExtraDownloadsRepository.php b/src/Composer/Repository/ExtraDownloadsRepository.php
new file mode 100644
index 0000000..b642ced
--- /dev/null
+++ b/src/Composer/Repository/ExtraDownloadsRepository.php
@@ -0,0 +1,144 @@
+<?php
+
+namespace LastCall\DownloadsPlugin\Composer\Repository;
+
+use Composer\Installer\InstallationManager;
+use Composer\Json\JsonFile;
+use Composer\Package\PackageInterface;
+use Composer\Repository\InstalledRepositoryInterface;
+use Composer\Repository\InvalidRepositoryException;
+use LastCall\DownloadsPlugin\Composer\Package\ExtraDownloadInterface;
+
+class ExtraDownloadsRepository implements InstalledRepositoryInterface
+{
+    private array $extraDownloads;
+
+    public function __construct(private JsonFile $file)
+    {
+        $this->initialize();
+    }
+
+    public function addPackage(PackageInterface $package): void
+    {
+        if ($package instanceof ExtraDownloadInterface) {
+            $this->extraDownloads[$package->getName()] = $package->getTrackingChecksum();
+        }
+    }
+
+    public function removePackage(PackageInterface $package): void
+    {
+        if ($package instanceof ExtraDownloadInterface) {
+            unset($this->extraDownloads[$package->getName()]);
+        }
+    }
+
+    public function hasPackage(PackageInterface $package): bool
+    {
+        if (!$package instanceof ExtraDownloadInterface) {
+            return false;
+        }
+        $name = $package->getName();
+        if (!isset($this->extraDownloads[$name])) {
+            return false;
+        }
+
+        return $this->extraDownloads[$name] === $package->getTrackingChecksum();
+    }
+
+    protected function initialize(): void
+    {
+        $this->extraDownloads = [];
+
+        if (!$this->file->exists()) {
+            return;
+        }
+
+        try {
+            $packages = $this->file->read();
+
+            if (!\is_array($packages)) {
+                throw new \UnexpectedValueException('Could not parse package list from the repository');
+            }
+        } catch (\Exception $e) {
+            throw new InvalidRepositoryException('Invalid repository data in '.$this->file->getPath().', packages could not be loaded: ['.$e::class.'] '.$e->getMessage());
+        }
+
+        foreach ($packages as $name => $trackingChecksum) {
+            $this->extraDownloads[$name] = $trackingChecksum;
+        }
+    }
+
+    public function reload(): void
+    {
+        $this->initialize();
+    }
+
+    public function isFresh(): bool
+    {
+        return !$this->file->exists();
+    }
+
+    public function write(bool $devMode, InstallationManager $installationManager): void
+    {
+        $this->file->write($this->extraDownloads);
+    }
+
+    public function getCanonicalPackages(): array
+    {
+        return [];
+    }
+
+    public function getDevPackageNames(): array
+    {
+        return [];
+    }
+
+    public function setDevPackageNames(array $devPackageNames): void
+    {
+    }
+
+    public function count(): int
+    {
+        return 0;
+    }
+
+    public function getRepoName(): string
+    {
+        return 'installed extra downloads';
+    }
+
+    public function getDevMode(): ?bool
+    {
+        return null;
+    }
+
+    public function findPackage(string $name, $constraint): ?PackageInterface
+    {
+        return null;
+    }
+
+    public function findPackages(string $name, $constraint = null): array
+    {
+        return [];
+    }
+
+    public function getPackages(): array
+    {
+        return [];
+    }
+
+    public function getProviders(string $packageName): array
+    {
+        return [];
+    }
+
+    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array
+    {
+        return [];
+    }
+
+    public function search(string $query, int $mode = 0, ?string $type = null): array
+    {
+        return [];
+    }
+}
diff --git a/tests/Unit/Composer/Repository/ExtraDownloadsRepositoryTest.php b/tests/Unit/Composer/Repository/ExtraDownloadsRepositoryTest.php
new file mode 100644
index 0000000..15dc994
--- /dev/null
+++ b/tests/Unit/Composer/Repository/ExtraDownloadsRepositoryTest.php
@@ -0,0 +1,234 @@
+<?php
+
+namespace LastCall\DownloadsPlugin\Tests\Unit\Composer\Repository;
+
+use Composer\Installer\InstallationManager;
+use Composer\Json\JsonFile;
+use Composer\Package\PackageInterface;
+use Composer\Repository\InvalidRepositoryException;
+use LastCall\DownloadsPlugin\Composer\Package\ExtraDownloadInterface;
+use LastCall\DownloadsPlugin\Composer\Repository\ExtraDownloadsRepository;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Seld\JsonLint\ParsingException;
+use VirtualFileSystem\FileSystem;
+
+class ExtraDownloadsRepositoryTest extends TestCase
+{
+    private ?FileSystem $fs = null;
+    private PackageInterface|MockObject $package;
+    private ExtraDownloadInterface|MockObject $extraDownload;
+    private ExtraDownloadsRepository $repository;
+    private string $path = '/path/to/vendor/composer/installed-extra-downloads.json';
+    private string $name = 'vendor/package-name:extra-file';
+    private string $trackingChecksum = '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';
+
+    protected function setUp(): void
+    {
+        $this->fs = new FileSystem();
+        $this->package = $this->createMock(PackageInterface::class);
+        $this->extraDownload = $this->createMock(ExtraDownloadInterface::class);
+        $this->repository = new ExtraDownloadsRepository(new JsonFile($this->fs->path($this->path)));
+    }
+
+    protected function tearDown(): void
+    {
+        $this->fs = null;
+    }
+
+    public function testAddPackage(): void
+    {
+        $this->repository->addPackage($this->package);
+        $this->assertExtraDownloads([]);
+    }
+
+    public function testAddExtraDownload(): void
+    {
+        $this->extraDownload
+            ->expects($this->once())
+            ->method('getName')
+            ->willReturn($this->name);
+        $this->extraDownload
+            ->expects($this->once())
+            ->method('getTrackingChecksum')
+            ->willReturn($this->trackingChecksum);
+        $this->repository->addPackage($this->extraDownload);
+        $this->assertExtraDownloads([
+            $this->name => $this->trackingChecksum,
+        ]);
+    }
+
+    public function testRemovePackage(): void
+    {
+        $this->repository->removePackage($this->package);
+        $this->assertExtraDownloads([]);
+    }
+
+    public function testRemoveExtraDownload(): void
+    {
+        $this->extraDownload
+            ->expects($this->exactly(2))
+            ->method('getName')
+            ->willReturn($this->name);
+        $this->extraDownload
+            ->expects($this->once())
+            ->method('getTrackingChecksum')
+            ->willReturn($this->trackingChecksum);
+        $this->repository->addPackage($this->extraDownload);
+        $this->assertExtraDownloads([
+            $this->name => $this->trackingChecksum,
+        ]);
+        $this->repository->removePackage($this->extraDownload);
+        $this->assertExtraDownloads([]);
+    }
+
+    public function testHasPackage(): void
+    {
+        $this->assertFalse($this->repository->hasPackage($this->package));
+    }
+
+    public function getHasExtraDownloadTests(): array
+    {
+        return [
+            [false],
+            [true],
+        ];
+    }
+
+    /**
+     * @dataProvider getHasExtraDownloadTests
+     */
+    public function testHasExtraDownload(bool $sameTrackingChecksum): void
+    {
+        $this->extraDownload
+            ->expects($this->exactly(3))
+            ->method('getName')
+            ->willReturn($this->name);
+        $trackingChecksum = $sameTrackingChecksum ? $this->trackingChecksum : '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824';
+        $this->extraDownload
+            ->expects($this->exactly(2))
+            ->method('getTrackingChecksum')
+            ->willReturnOnConsecutiveCalls($this->trackingChecksum, $trackingChecksum);
+        $this->assertFalse($this->repository->hasPackage($this->extraDownload));
+        $this->repository->addPackage($this->extraDownload);
+        $this->assertSame($sameTrackingChecksum, $this->repository->hasPackage($this->extraDownload));
+    }
+
+    public function testReloadFromInvalidFile(): void
+    {
+        $this->fs->createDirectory(\dirname($this->path), true);
+        $this->fs->createFile($this->path, 'not json data');
+        $this->expectException(InvalidRepositoryException::class);
+        $this->expectExceptionMessage('Invalid repository data in '.$this->fs->path($this->path).', packages could not be loaded: ['.ParsingException::class.'] "'.$this->fs->path($this->path).'" does not contain valid JSON');
+        $this->repository->reload();
+    }
+
+    public function testReloadFromJsonString(): void
+    {
+        $this->fs->createDirectory(\dirname($this->path), true);
+        $this->fs->createFile($this->path, json_encode('some text'));
+        $this->expectException(InvalidRepositoryException::class);
+        $this->expectExceptionMessage('Invalid repository data in '.$this->fs->path($this->path).', packages could not be loaded: ['.\UnexpectedValueException::class.'] Could not parse package list from the repository');
+        $this->repository->reload();
+    }
+
+    public function testReload(): void
+    {
+        $this->fs->createDirectory(\dirname($this->path), true);
+        $this->fs->createFile($this->path, json_encode([
+            $this->name => $this->trackingChecksum,
+        ]));
+        $this->repository->reload();
+        $this->assertExtraDownloads([
+            $this->name => $this->trackingChecksum,
+        ]);
+    }
+
+    public function testIsFresh(): void
+    {
+        $this->assertTrue($this->repository->isFresh());
+        $this->fs->createDirectory(\dirname($this->path), true);
+        $this->fs->createFile($this->path, 'data');
+        $this->assertFalse($this->repository->isFresh());
+    }
+
+    public function testWrite(): void
+    {
+        $this->extraDownload
+            ->expects($this->once())
+            ->method('getName')
+            ->willReturn($this->name);
+        $this->extraDownload
+            ->expects($this->once())
+            ->method('getTrackingChecksum')
+            ->willReturn($this->trackingChecksum);
+        $this->repository->addPackage($this->extraDownload);
+        $this->assertFileDoesNotExist($this->fs->path($this->path));
+        $this->repository->write(true, $this->createMock(InstallationManager::class));
+        $this->assertFileExists($this->fs->path($this->path));
+        $this->assertJsonStringEqualsJsonString(json_encode([
+            $this->name => $this->trackingChecksum,
+        ]), file_get_contents($this->fs->path($this->path)));
+    }
+
+    private function assertExtraDownloads(array $extraDownloads): void
+    {
+        $reflection = new \ReflectionProperty($this->repository, 'extraDownloads');
+        $this->assertSame($extraDownloads, $reflection->getValue($this->repository));
+    }
+
+    public function testGetCanonicalPackages(): void
+    {
+        $this->assertSame([], $this->repository->getCanonicalPackages());
+    }
+
+    public function testGetDevPackageNames(): void
+    {
+        $this->assertSame([], $this->repository->getDevPackageNames());
+    }
+
+    public function testCount(): void
+    {
+        $this->assertSame(0, $this->repository->count());
+    }
+
+    public function testGetRepoName(): void
+    {
+        $this->assertSame('installed extra downloads', $this->repository->getRepoName());
+    }
+
+    public function testGetDevMode(): void
+    {
+        $this->assertNull($this->repository->getDevMode());
+    }
+
+    public function testFindPackage(): void
+    {
+        $this->assertNull($this->repository->findPackage('any package name', 'any constraint'));
+    }
+
+    public function testFindPackages(): void
+    {
+        $this->assertSame([], $this->repository->findPackages('any package name', 'any constraint'));
+    }
+
+    public function testGetPackages(): void
+    {
+        $this->assertSame([], $this->repository->getPackages());
+    }
+
+    public function testGetProviders(): void
+    {
+        $this->assertSame([], $this->repository->getProviders('any package name'));
+    }
+
+    public function testLoadPackages(): void
+    {
+        $this->assertSame([], $this->repository->loadPackages([], [], [], []));
+    }
+
+    public function testSearch(): void
+    {
+        $this->assertSame([], $this->repository->search('package query'));
+    }
+}