{"_id":"@0xordek/git-me","_rev":"6-eb9640f883f1913bbb368b5ce8a94f52","name":"@0xordek/git-me","dist-tags":{"latest":"0.5.1"},"versions":{"0.3.0":{"name":"@0xordek/git-me","version":"0.3.0","license":"MIT","_id":"@0xordek/git-me@0.3.0","maintainers":[{"name":"0xordek","email":"0xordek@gmail.com"}],"homepage":"https://github.com/0xordek/git-me#readme","bugs":{"url":"https://github.com/0xordek/git-me/issues"},"bin":{"git-me":"dist/cli.js"},"dist":{"shasum":"ea47f23df639fefdf99271cc891623a321def2a7","tarball":"https://registry.npmjs.org/@0xordek/git-me/-/git-me-0.3.0.tgz","fileCount":4,"integrity":"sha512-oze7TDwGzt3gg3/e5Nl0MF/rO+DypV86+9GJ57deFB/RiZ2EzhdWSS+xL+KI329TM1kxd2zJvK6UnzuDPrAd3A==","signatures":[{"sig":"MEUCIHXaCOor1xPc/HQjgufRKM2PKPCo0wzeOJTUsRhnmPfRAiEAzjqBiQJM6nP9tmR0rf6TgwU5j//8DWj1x8lrnQJxgQw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31903},"type":"module","engines":{"node":">=22"},"gitHead":"4e0733343feac7966b738557478e12845b1a36f6","scripts":{"test":"vitest run","build":"wrangler deploy --dry-run --config wrangler.example.toml --outdir dist --metafile dist/bundle-meta.json","check":"npm test && npm run typecheck && npm run build:cli && git diff --exit-code -- dist/cli.js && npm pack --dry-run && npm run deploy:dry","deploy":"wrangler deploy --config wrangler.local.toml","build:cli":"esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\"","typecheck":"tsc --noEmit","deploy:dry":"wrangler deploy --dry-run --config wrangler.example.toml","version:deploy":"wrangler versions deploy --config wrangler.local.toml","version:upload":"wrangler versions upload --config wrangler.local.toml"},"_npmUser":{"name":"0xordek","email":"0xordek@gmail.com"},"repository":{"url":"git+https://github.com/0xordek/git-me.git","type":"git"},"_npmVersion":"11.4.2","description":"Self-hosted Git LFS utility for Cloudflare Workers, R2, and Durable Objects.","directories":{},"_nodeVersion":"23.6.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.7","esbuild":"^0.28.1","wrangler":"^4.107.1","typescript":"^6.0.3","@types/node":"^26.1.0","@cloudflare/workers-types":"^4.20260702.1"},"_npmOperationalInternal":{"tmp":"tmp/git-me_0.3.0_1783685602146_0.7801057741176998","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@0xordek/git-me","version":"0.4.0","license":"MIT","_id":"@0xordek/git-me@0.4.0","maintainers":[{"name":"0xordek","email":"0xordek@gmail.com"}],"homepage":"https://github.com/0xordek/git-me#readme","bugs":{"url":"https://github.com/0xordek/git-me/issues"},"bin":{"git-me":"dist/cli.js"},"dist":{"shasum":"600aa95d5e299bfabc7d52255221b29e41350176","tarball":"https://registry.npmjs.org/@0xordek/git-me/-/git-me-0.4.0.tgz","fileCount":5,"integrity":"sha512-jCrck6tg4ow0a8cHzTIgKqx5+0+KrpvFDZL8mbTsdNfWWCrJPDzWzlSaYeZqmAIYGvpvEHFSUyo0k7PJEmcHDA==","signatures":[{"sig":"MEUCIQDRYEqWfxQzAVl1DxviLkhr8eu3TGmyTNuk1+BGwPP42gIgNRJ2y+c7G2Ha6QluYt3/2gXWdF4sMVcp8ztR1pZCnpY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@0xordek%2fgit-me@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":83395},"type":"module","engines":{"node":">=22"},"gitHead":"9e980724c49f65bb45a1e631765728bbbffd0ee7","scripts":{"test":"vitest run","build":"npm run build:worker","check":"npm test && npm run typecheck && npm run build:worker && npm run build:cli && git diff --exit-code -- dist/cli.js && npm pack --dry-run --json | node -e \"let data='';process.stdin.on('data',chunk=>data+=chunk).on('end',()=>{if(!JSON.parse(data)[0].files.some(file=>file.path==='dist/worker.js'))process.exit(1)})\" && npm run deploy:dry","deploy":"wrangler deploy --config wrangler.local.toml","prepack":"npm run build:worker && npm run build:cli","build:cli":"esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\"","typecheck":"tsc --noEmit","deploy:dry":"wrangler deploy --dry-run --config wrangler.example.toml","build:worker":"esbuild src/worker.ts --bundle --format=esm --platform=browser --outfile=dist/worker.js","version:deploy":"wrangler versions deploy --config wrangler.local.toml","version:upload":"wrangler versions upload --config wrangler.local.toml"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:440a6600-7981-4880-8204-52c5dcef9d88"}},"repository":{"url":"git+https://github.com/0xordek/git-me.git","type":"git"},"_npmVersion":"11.16.0","description":"Self-hosted Git LFS utility for Cloudflare Workers, R2, and Durable Objects.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"wrangler":"^4.107.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.7","esbuild":"^0.28.1","typescript":"^6.0.3","@types/node":"^26.1.0","@cloudflare/workers-types":"^4.20260702.1"},"_npmOperationalInternal":{"tmp":"tmp/git-me_0.4.0_1783708426239_0.7494898545618931","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@0xordek/git-me","version":"0.4.1","license":"MIT","_id":"@0xordek/git-me@0.4.1","maintainers":[{"name":"0xordek","email":"0xordek@gmail.com"}],"homepage":"https://github.com/0xordek/git-me#readme","bugs":{"url":"https://github.com/0xordek/git-me/issues"},"bin":{"git-me":"dist/cli.js"},"dist":{"shasum":"a2595d949816e38b7e46f43cdae64374b904f8f0","tarball":"https://registry.npmjs.org/@0xordek/git-me/-/git-me-0.4.1.tgz","fileCount":5,"integrity":"sha512-js1FNnJnTPa49sxKV1GzRqvMC7XAtDJnR7ShtkEsPXQiBKufPzs4iu/jheXemWwQT3d+rR2eqLeitzNdnocAwg==","signatures":[{"sig":"MEUCIA7qOFUg5sHM+gSpZM8e4Khuqo2dPoUna2MplyGazS4cAiEAojIA/uMJKJWIx8xrwHjxeyOEw6ywMhncWakN/FtX98s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@0xordek%2fgit-me@0.4.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":83676},"type":"module","engines":{"node":">=22"},"gitHead":"5c2075a30f54cfa501379daf8e19a3bda7631cff","scripts":{"test":"vitest run","build":"npm run build:worker","check":"npm run build:worker && npm run build:cli && npm test && npm run typecheck && git diff --exit-code -- dist/cli.js && npm pack --dry-run --json | node -e \"let data='';process.stdin.on('data',chunk=>data+=chunk).on('end',()=>{const files=new Set(JSON.parse(data)[0].files.map(file=>file.path));if(!['dist/cli.js','dist/worker.js'].every(file=>files.has(file)))process.exit(1)})\" && npm run deploy:dry","deploy":"wrangler deploy --config wrangler.local.toml","prepack":"npm run build:worker && npm run build:cli","build:cli":"esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\"","typecheck":"tsc --noEmit","deploy:dry":"wrangler deploy --dry-run --config wrangler.example.toml","build:worker":"esbuild src/worker.ts --bundle --format=esm --platform=browser --outfile=dist/worker.js","version:deploy":"wrangler versions deploy --config wrangler.local.toml","version:upload":"wrangler versions upload --config wrangler.local.toml"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:440a6600-7981-4880-8204-52c5dcef9d88"}},"repository":{"url":"git+https://github.com/0xordek/git-me.git","type":"git"},"_npmVersion":"11.16.0","description":"Self-hosted Git LFS utility for Cloudflare Workers, R2, and Durable Objects.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"wrangler":"^4.107.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.7","esbuild":"^0.28.1","typescript":"^6.0.3","@types/node":"^26.1.0","@cloudflare/workers-types":"^4.20260702.1"},"_npmOperationalInternal":{"tmp":"tmp/git-me_0.4.1_1783803401113_0.6468920405498604","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@0xordek/git-me","version":"0.5.0","license":"MIT","_id":"@0xordek/git-me@0.5.0","maintainers":[{"name":"0xordek","email":"0xordek@gmail.com"}],"homepage":"https://github.com/0xordek/git-me#readme","bugs":{"url":"https://github.com/0xordek/git-me/issues"},"bin":{"git-me":"dist/cli.js"},"dist":{"shasum":"78821dece1d81113c37eec31ee92e61f4a75fc22","tarball":"https://registry.npmjs.org/@0xordek/git-me/-/git-me-0.5.0.tgz","fileCount":5,"integrity":"sha512-P/JjZieQ4c2WeX7IBD4UI/IJz+CaDdLOEH5N9X4K2BqaSO+UnA7s1tA9uwA8VDJ1n3F1hX4/N3SKzVsWYFWPRQ==","signatures":[{"sig":"MEUCIFo76xiKjuUV6sYqxKc1mSC8FWZMYlgwC9Fvg8t1LtPjAiEAyMTDVVKuoncq+eCOJOsxQCgywqe0raA5vGhlzf62eJ0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@0xordek%2fgit-me@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":86561},"type":"module","engines":{"node":">=22"},"gitHead":"1205cf8e763a2aaae55930ea6edc73f8fb8b225e","scripts":{"test":"npm run test:node && npm run test:workers","build":"npm run build:worker","check":"npm run build:worker && npm run build:cli && npm test && npm run typecheck && npm run types:check && git diff --exit-code -- dist/cli.js && npm pack --dry-run --json | node -e \"let data='';process.stdin.on('data',chunk=>data+=chunk).on('end',()=>{const files=new Set(JSON.parse(data)[0].files.map(file=>file.path));if(!['dist/cli.js','dist/worker.js'].every(file=>files.has(file)))process.exit(1)})\" && npm run deploy:dry","deploy":"wrangler deploy --config wrangler.local.toml","prepack":"npm run build:worker && npm run build:cli","build:cli":"esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\"","test:node":"vitest run --config vitest.config.ts","typecheck":"tsc --noEmit","deploy:dry":"wrangler deploy --dry-run --config wrangler.example.toml","types:check":"wrangler types worker-configuration.d.ts --config wrangler.example.toml --env-interface CloudflareBindings --include-runtime=false --check","build:worker":"esbuild src/worker.ts --bundle --format=esm --platform=browser --external:cloudflare:* --outfile=dist/worker.js","test:workers":"vitest run --config vitest.worker.config.ts","version:deploy":"wrangler versions deploy --config wrangler.local.toml","version:upload":"wrangler versions upload --config wrangler.local.toml"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:440a6600-7981-4880-8204-52c5dcef9d88"}},"repository":{"url":"git+https://github.com/0xordek/git-me.git","type":"git"},"_npmVersion":"11.16.0","description":"Self-hosted Git LFS utility for Cloudflare Workers, R2, and Durable Objects.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"wrangler":"^4.110.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.10","esbuild":"^0.28.1","typescript":"^6.0.3","@types/node":"^26.1.1","@cloudflare/workers-types":"^5.20260712.1","@cloudflare/vitest-pool-workers":"^0.18.4"},"_npmOperationalInternal":{"tmp":"tmp/git-me_0.5.0_1785185262870_0.5991312830590227","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@0xordek/git-me","version":"0.6.0","license":"MIT","_id":"@0xordek/git-me@0.6.0","maintainers":[{"name":"0xordek","email":"0xordek@gmail.com"}],"homepage":"https://github.com/0xordek/git-me#readme","bugs":{"url":"https://github.com/0xordek/git-me/issues"},"bin":{"git-me":"dist/cli.js"},"dist":{"shasum":"a5225b2a5168f2fd3f7db140a490c8de905690ba","tarball":"https://registry.npmjs.org/@0xordek/git-me/-/git-me-0.6.0.tgz","fileCount":5,"integrity":"sha512-QH6e8qTu6PYrYuswcRhwSSFaHcwH6Cfv1PM1RJTSNx8wtxI4otHk8peO+PXe7WwgQ2BQSTEz05s1gg1arl9bWw==","signatures":[{"sig":"MEUCIQC4efaGDw969HsRX3KHFSZROY9rKE0xRLrmQZjCy1+l8QIgXX3jOozNRFihNNLR6m/jBPdxzEV9t60tinNHd/QGcS0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@0xordek%2fgit-me@0.6.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":95337},"type":"module","engines":{"node":">=22"},"gitHead":"f06ba41b9760c56a2d180fa949755e667214f305","scripts":{"test":"npm run test:node && npm run test:workers","build":"npm run build:worker","check":"npm run build:worker && npm run build:cli && npm test && npm run typecheck && npm run types:check && git diff --exit-code -- dist/cli.js && npm pack --dry-run --json | node -e \"let data='';process.stdin.on('data',chunk=>data+=chunk).on('end',()=>{const files=new Set(JSON.parse(data)[0].files.map(file=>file.path));if(!['dist/cli.js','dist/worker.js'].every(file=>files.has(file)))process.exit(1)})\" && npm run deploy:dry","deploy":"wrangler deploy --config wrangler.local.toml","prepack":"npm run build:worker && npm run build:cli","build:cli":"esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\"","test:node":"vitest run --config vitest.config.ts","typecheck":"tsc --noEmit","deploy:dry":"wrangler deploy --dry-run --config wrangler.example.toml","types:check":"wrangler types worker-configuration.d.ts --config wrangler.example.toml --env-interface CloudflareBindings --include-runtime=false --check","build:worker":"esbuild src/worker.ts --bundle --format=esm --platform=browser --external:cloudflare:* --outfile=dist/worker.js","test:workers":"vitest run --config vitest.worker.config.ts","version:deploy":"wrangler versions deploy --config wrangler.local.toml","version:upload":"wrangler versions upload --config wrangler.local.toml"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:440a6600-7981-4880-8204-52c5dcef9d88"}},"repository":{"url":"git+https://github.com/0xordek/git-me.git","type":"git"},"_npmVersion":"11.17.0","description":"Self-hosted Git LFS utility for Cloudflare Workers, R2, and Durable Objects.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"wrangler":"^4.116.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.10","esbuild":"^0.28.1","typescript":"^6.0.3","@types/node":"^26.1.1","@cloudflare/workers-types":"^5.20260712.1","@cloudflare/vitest-pool-workers":"^0.19.1"},"_npmOperationalInternal":{"tmp":"tmp/git-me_0.6.0_1787598766724_0.5004183320148587","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"@0xordek/git-me","version":"0.5.1","description":"Self-hosted Git LFS utility for Cloudflare Workers, R2, and Durable Objects.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/0xordek/git-me.git"},"homepage":"https://github.com/0xordek/git-me#readme","bugs":{"url":"https://github.com/0xordek/git-me/issues"},"engines":{"node":">=22"},"publishConfig":{"access":"public"},"type":"module","bin":{"git-me":"dist/cli.js"},"dependencies":{"wrangler":"^4.116.0"},"scripts":{"build":"npm run build:worker","test":"npm run test:node && npm run test:workers","test:node":"vitest run --config vitest.config.ts","test:workers":"vitest run --config vitest.worker.config.ts","typecheck":"tsc --noEmit","types:check":"wrangler types worker-configuration.d.ts --config wrangler.example.toml --env-interface CloudflareBindings --include-runtime=false --check","build:cli":"esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --banner:js=\"#!/usr/bin/env node\"","prepack":"npm run build:worker && npm run build:cli","check":"npm run build:worker && npm run build:cli && npm test && npm run typecheck && npm run types:check && git diff --exit-code -- dist/cli.js && npm pack --dry-run --json | node -e \"let data='';process.stdin.on('data',chunk=>data+=chunk).on('end',()=>{const files=new Set(JSON.parse(data)[0].files.map(file=>file.path));if(!['dist/cli.js','dist/worker.js'].every(file=>files.has(file)))process.exit(1)})\" && npm run deploy:dry","build:worker":"esbuild src/worker.ts --bundle --format=esm --platform=browser --external:cloudflare:* --outfile=dist/worker.js","deploy:dry":"wrangler deploy --dry-run --config wrangler.example.toml","deploy":"wrangler deploy --config wrangler.local.toml","version:upload":"wrangler versions upload --config wrangler.local.toml","version:deploy":"wrangler versions deploy --config wrangler.local.toml"},"devDependencies":{"@cloudflare/vitest-pool-workers":"^0.19.1","@cloudflare/workers-types":"^5.20260712.1","@types/node":"^26.1.1","esbuild":"^0.28.1","typescript":"^6.0.3","vitest":"^4.1.10"},"gitHead":"76e4002d82e0a3f2b4890899099833de815d432b","_id":"@0xordek/git-me@0.5.1","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-/BM2c07WP9gATH+XRwi3fIJfGuGmHh6y3fP01y0LR+L2QzZGl6R2MX6/dN/xyojE2ZoAqtT4zHQABUSj8phKlQ==","shasum":"beb585b75e2e0830dbeb625c082d6fe1d0846a5d","tarball":"https://registry.npmjs.org/@0xordek/git-me/-/git-me-0.5.1.tgz","fileCount":5,"unpackedSize":92801,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@0xordek%2fgit-me@0.5.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAo18RjsXAnXcquDsQYxJTk2Bs2WGg6uijNHYtkG/cDMAiBaOETvVfx2I+0RkvrAARD5yijuqPXW1Z8JZc6qX6KdrA=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:440a6600-7981-4880-8204-52c5dcef9d88"}},"directories":{},"maintainers":[{"name":"0xordek","email":"0xordek@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/git-me_0.5.1_1787598769953_0.4932781064169489"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-10T12:13:21.922Z","modified":"2026-08-24T19:12:50.566Z","0.3.0":"2026-07-10T12:13:22.276Z","0.4.0":"2026-07-10T18:33:46.398Z","0.4.1":"2026-07-11T20:56:41.249Z","0.5.0":"2026-07-27T20:47:43.021Z","0.6.0":"2026-08-24T19:12:46.856Z","0.5.1":"2026-08-24T19:12:50.137Z"},"bugs":{"url":"https://github.com/0xordek/git-me/issues"},"license":"MIT","homepage":"https://github.com/0xordek/git-me#readme","repository":{"type":"git","url":"git+https://github.com/0xordek/git-me.git"},"description":"Self-hosted Git LFS utility for Cloudflare Workers, R2, and Durable Objects.","maintainers":[{"name":"0xordek","email":"0xordek@gmail.com"}],"readme":"# git-me\n\nA zero-config self-hosted Git LFS service for Cloudflare Workers, R2, Durable Objects, and KV-backed legacy-user migration.\n\nBuilt as a TypeScript Cloudflare Worker.\n\n## Zero-Config Quick Start\n\nRequires Node.js 22+ and a Cloudflare account. Wrangler is bundled as the deploy engine; users do not need to install or configure it.\n\n```bash\nnpm install -g @0xordek/git-me\ngit-me worker deploy\n```\n\nThe command opens Cloudflare login, creates the Worker, R2 bucket, KV namespace, and Durable Object migration, generates the admin secret, stores it in the operating system credential store, checks `/health`, and saves a local profile.\n\nUse `--account-id <id>` when Cloudflare login has more than one account. A profile name cannot be deployed twice; use another `--profile <name>` for another Worker.\n\nExample output:\n\n```text\nDeployed: https://git-me-abc.workers.dev\nProfile: default\nLFS URL: https://git-me-abc.workers.dev\n```\n\nAdd and manage users without copying Worker URLs or admin tokens:\n\n```bash\ngit-me user add 0xordek --access write\ngit-me user list\ngit-me user list --json\ngit-me user delete 0xordek\n```\n\nPasswords are read from a hidden prompt. For automation, use stdin; secrets are never accepted as command arguments:\n\n```bash\nprintf '%s' \"$LFS_PASSWORD\" | git-me user add 0xordek --access write --password-stdin\ngit-me user delete 0xordek --yes\n```\n\nIf the operating system credential store is unavailable, pass the admin secret explicitly with `--token-stdin` or `--token-env`.\n\nExisting users created before user listing are added to the list after their next successful login; Durable Objects cannot enumerate them safely.\n\n### macOS credential recovery for 0.4.0–0.4.1\n\nThose releases could deploy successfully without saving the generated admin secret in Keychain. If the profile cannot find its credential, rotate `GITME_AUTH_TOKEN` in the Cloudflare dashboard (or with `wrangler secret put` and a local config), then save the same replacement locally without putting it in a process argument:\n\n```bash\nread -rsp 'New admin token: ' GITME_AUTH_TOKEN; echo\nprintf '%s' \"$GITME_AUTH_TOKEN\" | security add-generic-password \\\n  -U -a git-me -s git-me:default:admin -w\nunset GITME_AUTH_TOKEN\n```\n\nReplace `default` in the Keychain service name for another profile. Environment and stdin token options remain available if Keychain cannot be used.\n\n### User creation recovery for 0.3.0–0.5.0\n\nThose releases derived password records with 600,000 PBKDF2 iterations, above the 100,000 iteration limit Workers WebCrypto accepts. Every `git-me user add` against a deployed Worker fails with HTTP 500 and the Worker logs `Pbkdf2 failed: iteration counts above 100000 are not supported`. Redeploy the Worker with 0.5.1 or later, then create the users again:\n\n```bash\ngit-me worker deploy --profile <name>\ngit-me user add <username> --profile <name> --access write\n```\n\nPassword records written by those releases cannot exist on Cloudflare, because creation never succeeded. Records written by a self-hosted `workerd` without the limit keep working; 0.5.1 stores the iteration count with each record and verifies pre-0.5.1 records at 600,000.\n\n## Quick Start (development)\n\n```bash\ngit clone git@github.com:0xordek/git-me.git\ncd git-me\nnpm ci\nnpm run check\n```\n\n## Manual Cloudflare Setup (development/legacy)\n\nThe zero-config path above is recommended for users. The manual setup remains useful for local Worker development and existing deployments.\n\nCreate storage resources:\n\n```bash\nwrangler r2 bucket create git-me-objects\nwrangler kv namespace create git-me-metadata\n```\n\nWrangler prints the KV namespace id. Create private deployment config, then replace its placeholder ID:\n\n```bash\ncp wrangler.example.toml wrangler.local.toml\n```\n\n`wrangler.local.toml` is ignored by Git. Before a real deploy, replace its placeholder KV namespace ID with your namespace ID. `npm run deploy` creates the `AuthUser` Durable Object migration.\n\nSet the admin token as a secret:\n\n```bash\nwrangler secret put GITME_AUTH_TOKEN\n```\n\n## Transfer Modes\n\n- `proxy`: default mode. The Worker handles Git LFS upload and download bytes through the `PUT /objects/{oid}` and `GET /objects/{oid}` endpoints.\n- `direct`: opt-in download acceleration. Uploads still use the Worker so every object gets SHA-256 verification; downloads receive short-lived signed R2 `GET` URLs.\n\nLeave `GITME_TRANSFER_MODE` unset for `proxy`, or set it to `direct` to opt in to signed R2 downloads. Give direct mode a bucket-scoped, read-only R2 S3 API token.\n\n## Limits\n\nUploads pass through the Worker in both transfer modes, so they inherit the Cloudflare 100 MB request-body limit. Cloudflare rejects a larger `PUT /objects/{oid}` at the edge with HTTP 413 before the Worker runs, so nothing appears in Worker logs and `git lfs push` reports the failure without a server-side trace. Downloads have no such limit.\n\nUntil presigned or multipart uploads land, write objects above 100 MB straight to R2 through the S3-compatible endpoint. Verify the local digest against the OID first, because a direct write bypasses the Worker's SHA-256 check:\n\n```bash\nsha256sum .git/lfs/objects/<xx>/<yy>/<oid>\naws s3api put-object \\\n  --endpoint-url \"https://<account-id>.r2.cloudflarestorage.com\" \\\n  --bucket <bucket> --key \"objects/<oid>\" \\\n  --body \".git/lfs/objects/<xx>/<yy>/<oid>\" \\\n  --metadata \"sha256=<oid>\"\n```\n\nThe `sha256=<oid>` user metadata becomes the R2 `customMetadata` marker the Worker writes after a verified proxy upload. Downloads work without it, but an upload batch keeps asking the client to send the object again, and `direct` mode falls back to proxy downloads. `wrangler r2 object put` cannot set custom metadata.\n\nA single `AuthUser` Durable Object handles every request for one username and runs PBKDF2 per request. Keep `git config lfs.concurrenttransfers 2` for bulk pushes; the default of 8 can saturate the object and return HTTP 503.\n\n## Configuration\n\n| Name | Required | Purpose |\n|------|----------|---------|\n| `GITME_AUTH_TOKEN` | Yes | Admin bearer token for user management and emergency LFS access |\n| `GITME_TRANSFER_MODE` | No | `proxy` by default; set `direct` for signed R2 downloads |\n| `GITME_SIGNED_URL_TTL_SECONDS` | No | Signed URL TTL for `direct` mode; defaults to `900` |\n| `GITME_R2_ACCOUNT_ID` | Direct mode | Cloudflare account id for R2 S3-compatible signing |\n| `GITME_R2_ACCESS_KEY_ID` | Direct mode | Read-only R2 API access key id |\n| `GITME_R2_SECRET_ACCESS_KEY` | Direct mode | Read-only R2 API secret access key |\n| `GITME_R2_BUCKET_NAME` | Direct mode | R2 bucket used in signed download URLs |\n\nUse Wrangler secrets for sensitive values:\n\n```bash\nwrangler secret put GITME_R2_SECRET_ACCESS_KEY\n```\n\nDeploy:\n\n```bash\nnpm run deploy:dry\nnpm run deploy\n```\n\n## Git LFS Client Setup\n\nFor the zero-config flow, use the profile-based commands above. The explicit URL/token form remains available for another machine or when the credential store is unavailable; pass secrets through environment variables or standard input, never command arguments:\n\n```bash\nread -rsp 'Admin token: ' GITME_ADMIN_TOKEN; echo\nexport GITME_ADMIN_TOKEN\nread -rsp 'LFS password: ' GITME_LFS_PASSWORD; echo\nprintf '%s' \"$GITME_LFS_PASSWORD\" | npx @0xordek/git-me user add \\\n  --target https://your-worker.workers.dev \\\n  --token-env GITME_ADMIN_TOKEN \\\n  --username alice \\\n  --password-stdin \\\n  --access write\nunset GITME_ADMIN_TOKEN GITME_LFS_PASSWORD\n```\n\nUse `\"read\"` for pull-only users and `\"write\"` for pull/push users. Delete access with:\n\n```bash\nread -rsp 'Admin token: ' GITME_ADMIN_TOKEN; echo\nexport GITME_ADMIN_TOKEN\nnpx @0xordek/git-me user delete \\\n  --target https://your-worker.workers.dev \\\n  --token-env GITME_ADMIN_TOKEN \\\n  --username alice\nunset GITME_ADMIN_TOKEN\n```\n\nConfigure a repo:\n\n```bash\ngit lfs track \"*.psd\" \"*.zip\" \"*.bin\"\ngit add .gitattributes\n\ngit config lfs.url https://your-worker.workers.dev\ngit config lfs.http.https://your-worker.workers.dev.locksverify false\n```\n\nFor shared repositories, commit `.lfsconfig` so fresh clones use the Worker too:\n\n```bash\ngit config -f .lfsconfig lfs.url https://your-worker.workers.dev\ngit add .lfsconfig\ngit commit -m \"chore: configure git-me lfs\"\n```\n\nThen use `git push` and `git pull` as normal. On first LFS access, Git asks for username and password. Git Credential Manager stores it on the machine, so deleting and cloning the repo again usually does not ask while the worker host stays the same.\n\nThe admin bearer token also works for LFS as an emergency write credential, but normal users should use Basic auth users created through `/admin/users/{username}`.\n\nIf you do not want the CLI, the same operations are available through `GET /admin/users`, `PUT /admin/users/{username}`, and `DELETE /admin/users/{username}`.\n\n## Migrating Existing LFS Objects\n\nUse the migration CLI from a local repository to copy objects from the current LFS server into `git-me`:\n\n```bash\nread -rsp 'Target token: ' GITME_TARGET_TOKEN; echo\nexport GITME_TARGET_TOKEN\nnpx @0xordek/git-me migrate --target https://your-worker.workers.dev --token-env GITME_TARGET_TOKEN --dry-run\nunset GITME_TARGET_TOKEN\n```\n\nRun `--dry-run` first. It scans local Git LFS pointer files, deduplicates object IDs, and reports `scanned`, `unique`, `migrated`, `skipped`, and `failed` without transferring object bytes or writing Git config.\n\nFor a GitHub source, pass the source LFS URL explicitly when it is not already in `git config lfs.url`:\n\n```bash\nread -rsp 'Target token: ' GITME_TARGET_TOKEN; echo\nexport GITME_TARGET_TOKEN\nnpx @0xordek/git-me migrate \\\n  --source-url https://github.com/OWNER/REPO.git/info/lfs \\\n  --target https://your-worker.workers.dev \\\n  --token-env GITME_TARGET_TOKEN \\\n  --dry-run\nunset GITME_TARGET_TOKEN\n```\n\nFor another Git LFS server, use its Batch API base URL as `--source-url`:\n\n```bash\nread -rsp 'Target token: ' GITME_TARGET_TOKEN; echo\nexport GITME_TARGET_TOKEN\nnpx @0xordek/git-me migrate \\\n  --source-url https://source.example.com/repo.git/info/lfs \\\n  --target https://your-worker.workers.dev \\\n  --token-env GITME_TARGET_TOKEN \\\n  --dry-run\nunset GITME_TARGET_TOKEN\n```\n\nPrivate source repositories usually require an extra source header. Repeat `--source-header-env` for every environment variable containing a `name: value` header:\n\n```bash\nread -rsp 'Target token: ' GITME_TARGET_TOKEN; echo\nexport GITME_TARGET_TOKEN\nread -rsp 'Source header: ' GITME_SOURCE_AUTH; echo\nexport GITME_SOURCE_AUTH\nnpx @0xordek/git-me migrate \\\n  --source-url https://github.com/OWNER/PRIVATE-REPO.git/info/lfs \\\n  --source-header-env GITME_SOURCE_AUTH \\\n  --target https://your-worker.workers.dev \\\n  --token-env GITME_TARGET_TOKEN \\\n  --dry-run\nunset GITME_TARGET_TOKEN GITME_SOURCE_AUTH\n```\n\nSecret-bearing targets, sources, and action URLs must use HTTPS. Plain HTTP is accepted only for loopback development (`localhost`, `127.0.0.1`, or `::1`), and embedded URL credentials are rejected.\n\nAfter a successful real migration, add `--write-config` to update the repository's `lfs.url` to the target:\n\n```bash\nread -rsp 'Target token: ' GITME_TARGET_TOKEN; echo\nexport GITME_TARGET_TOKEN\nnpx @0xordek/git-me migrate \\\n  --target https://your-worker.workers.dev \\\n  --token-env GITME_TARGET_TOKEN \\\n  --write-config\nunset GITME_TARGET_TOKEN\n```\n\nSafety model: the CLI uses the generic Git LFS Batch API for source downloads and target uploads. Object bytes are streamed through temporary files named `git-me-migrate-*` in the OS temp directory, not buffered in memory, and each downloaded file must match the pointer SHA-256 OID before upload. Temporary files are removed after each object attempt, and `--write-config` only runs when the migration has no failures.\n\n## API Endpoints\n\n| Method | Path | Purpose |\n|--------|------|---------|\n| POST | `/objects/batch` | Git LFS Batch API |\n| PUT | `/objects/{oid}` | Upload object bytes |\n| GET | `/objects/{oid}` | Download object bytes |\n| PUT | `/admin/users/{username}` | Create or update LFS user |\n| GET | `/admin/users` | List usernames and access levels |\n| DELETE | `/admin/users/{username}` | Delete LFS user |\n| GET | `/health` | Configuration health check |\n\nBatch requests and error responses use `application/vnd.git-lfs+json`.\n`GET /health` returns `application/json`.\n\n## Storage Layout\n\n- R2 object key: `objects/<oid>`\n- Temporary proxy-upload key prefix: `objects/.tmp/`\n- Durable Object: one `AuthUser` instance per normalized username, plus reserved `admin:users` instance for the transactional admin index\n- KV user key: `user:<username>` only during legacy SHA-256 credential upgrade\n\n## Development\n\n```bash\nnpm ci\nnpm run check\nwrangler dev --config wrangler.local.toml\n```\n\n## Security\n\nDo not commit auth tokens or R2 API secrets. Use `wrangler secret put GITME_AUTH_TOKEN` and `wrangler secret put GITME_R2_SECRET_ACCESS_KEY`.\n\n`proxy` uploads stream through the Worker and must match their Git LFS SHA-256 OID before becoming readable. `direct` mode only signs R2 downloads; it never signs uploads. Use a bucket-scoped, read-only R2 credential for direct mode.\n\nExisting deployments upgraded from a release that signed direct uploads must first set `GITME_TRANSFER_MODE=proxy`, audit every `objects/<oid>` object against its SHA-256 filename, and quarantine mismatches. Rotate the old R2 S3 API token before enabling direct downloads again. Objects without the `v0.3` Worker verification marker always fall back to proxy downloads, even when `direct` mode is enabled.\n\nNew passwords use salted PBKDF2-SHA-256 records in Durable Objects. Existing KV SHA-256 records upgrade after one successful login. Deleted users leave a Durable Object tombstone, so stale KV reads cannot restore access. Authentication locks one client source for one username for one minute after five failed attempts in one minute.\n\n`GET /health` checks configuration only. It does not prove R2, KV, or Durable Object availability. Failed requests emit a request ID in `X-Request-Id`; logs never include credentials or request bodies.\n\nBearer credentials are compared in constant time after fixed-length hashing. Basic credentials are decoded as UTF-8. Proxy uploads stream into temporary R2 objects, settle both storage and digest work, and attempt temporary cleanup on every outcome.\n\nReport vulnerabilities through GitHub Security Advisories. See `SECURITY.md`.\n\n## Contributing\n\nSmall fixes and focused issues are welcome. See `CONTRIBUTING.md`.\n\n## License\n\nMIT — see `LICENSE`.\n","readmeFilename":"README.md"}