{"_id":"@abhishekvardanbotta/dv","name":"@abhishekvardanbotta/dv","dist-tags":{"latest":"0.9.0"},"versions":{"0.9.0":{"name":"@abhishekvardanbotta/dv","version":"0.9.0","description":"One CLI for GitHub, Vercel, Gmail, Calendar and LinkedIn - with OAuth built in and cross-service workflows.","type":"module","bin":{"dv":"dist/index.js"},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20"},"scripts":{"build":"tsc","dev":"tsx src/index.ts","test":"vitest run","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["cli","github","vercel","gmail","linkedin","oauth","devops","deploy","workflow"],"license":"MIT","devDependencies":{"@types/node":"^22.10.2","tsx":"^4.19.2","typescript":"^5.7.2","vitest":"^3.2.4"},"peerDependencies":{"@abhishekvardanbotta/kiln":">=0.4.0"},"peerDependenciesMeta":{"@abhishekvardanbotta/kiln":{"optional":true}},"author":{"name":"Abhishek Vardan Botta"},"publishConfig":{"access":"public"},"_id":"@abhishekvardanbotta/dv@0.9.0","gitHead":"a067172830e829b40128ca3c1dc066527cdb739d","_nodeVersion":"24.11.1","_npmVersion":"10.5.1","dist":{"integrity":"sha512-W/g6l6wkyiW2W5x4TIaE3zzg38lpkRJfX0LVl47s1SkE8g7W1HS73DgxeBH+lUZ6AFKXoHRh/E/PH4IOeUuG8w==","shasum":"e5537cc3b31932f9f92183390d9e16ad002ac980","tarball":"https://registry.npmjs.org/@abhishekvardanbotta/dv/-/dv-0.9.0.tgz","fileCount":154,"unpackedSize":1006319,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGeiJ/fmXTplaBrsH4iyg6j4ASuZZn3veue7cJAuQEaXAiBdJmzcrZAS8VxId9QcCmODw46b3+gx1pD+8NrfRoeisA=="}]},"_npmUser":{"name":"abhishekvardanbotta","email":"abhiuma2005@gmail.com"},"directories":{},"maintainers":[{"name":"abhishekvardanbotta","email":"abhiuma2005@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dv_0.9.0_1787473354336_0.5810036179954743"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-23T08:22:34.133Z","0.9.0":"2026-08-23T08:22:34.499Z","modified":"2026-08-23T08:22:34.730Z"},"maintainers":[{"name":"abhishekvardanbotta","email":"abhiuma2005@gmail.com"}],"description":"One CLI for GitHub, Vercel, Gmail, Calendar and LinkedIn - with OAuth built in and cross-service workflows.","keywords":["cli","github","vercel","gmail","linkedin","oauth","devops","deploy","workflow"],"author":{"name":"Abhishek Vardan Botta"},"license":"MIT","readme":"# dv\r\n\r\nOne CLI for GitHub, Gmail, Calendar, LinkedIn and Vercel — with OAuth built in.\r\n\r\n```bash\r\nnpm install -g @abhishekvardanbotta/dv\r\ndv auth login github\r\ndv gh repo list\r\n```\r\n\r\nNo runtime dependencies. Node 20+.\r\n\r\n---\r\n\r\n## Using dv from a coding assistant\r\n\r\n`dv` is built to be driven by Claude Code, Cursor, or any agent with a terminal —\r\nstdout/stderr are separated, every command takes `--json`, and the exit codes are a\r\ncontract rather than decoration.\r\n\r\nAn assistant that has **not** read the skill file treats `dv` as a thin wrapper and\r\nmisses the parts that matter: that exit 6 means nothing happened, that exit 3 should\r\nnever be retried, and that a policy rule is not something to route around. Install it\r\nonce:\r\n\r\n```bash\r\ndv skill install\r\n```\r\n\r\nThat copies the bundled skill to `~/.claude/skills/dv/SKILL.md` — no download, it ships\r\ninside the package. Start a new session afterwards to pick it up.\r\n\r\n**[📄 Read or download SKILL.md](https://unpkg.com/@abhishekvardanbotta/dv/SKILL.md)**\r\n· [via jsDelivr](https://cdn.jsdelivr.net/npm/@abhishekvardanbotta/dv/SKILL.md)\r\n\r\nFor a different assistant, or to place it by hand:\r\n\r\n```bash\r\ndv skill show                      # print it\r\ndv skill path                      # where the bundled copy lives\r\ndv skill install --to ./AGENTS.md  # or anywhere else\r\n\r\n# without installing dv first\r\ncurl -o SKILL.md https://unpkg.com/@abhishekvardanbotta/dv/SKILL.md\r\n```\r\n\r\nThe short version, if you are an assistant reading this instead:\r\n\r\n| Rule | |\r\n|---|---|\r\n| `--json` whenever you intend to parse | Data on stdout, progress on stderr |\r\n| `--yes` on every write | Without a terminal it refuses rather than hanging |\r\n| Branch on the exit code | `0` ok · `1` failed · `2` bad usage · `3` not authed · `4` provider refused · `5` not found · `6` declined |\r\n| `dv doctor` before debugging by hand | Checks accounts, repo, deploy, env and database in one pass |\r\n| Never work around `dv policy` | `--yes` does not satisfy a rule, and there is no bypass flag |\r\n\r\n---\r\n\r\n## Connecting\r\n\r\n14 connectors ship built in, and you can add any HTTP API yourself.\r\n\r\n```bash\r\ndv connect list             # everything, and how each one connects\r\ndv connect show notion      # base URL, auth style, setup steps\r\n```\r\n\r\nEach service is connected the way it actually allows — that is a constraint, not a\r\npreference:\r\n\r\n| Service | Ways in | Notes |\r\n|---|---|---|\r\n| GitHub | device, token, loopback | Device flow needs no port and works over SSH |\r\n| Google | loopback + PKCE | No device flow exists for Gmail/Calendar scopes |\r\n| LinkedIn | loopback, token | Needs a client secret — no PKCE support |\r\n| Atlassian | basic, loopback | Email + API token, per your own site |\r\n| Slack, Notion, Linear, Discord, Figma | token, loopback | |\r\n| Vercel, Supabase, Stripe, OpenAI | token | No user-facing OAuth for CLIs |\r\n| Anthropic | header | Uses `x-api-key`, not `Authorization` |\r\n\r\n`dv auth login <service>` asks how, with arrow keys:\r\n\r\n```\r\nHow would you like to connect to GitHub?\r\n  up/down to move, Enter to select, Ctrl-C to cancel\r\n\r\n  > Browser code     Shows a code to paste - no port, works over SSH  (recommended)\r\n    Paste a token    A personal access token or API key\r\n    Browser sign-in  Opens a browser and listens on 127.0.0.1\r\n```\r\n\r\nArrows or `j`/`k` move, the list wraps at both ends, digits still select directly, and\r\nEnter takes the recommended option. Picking \"Paste a token\" then tells you where to get\r\none and reads it without echoing it.\r\n\r\nSkip the questions entirely when you already know:\r\n\r\n```bash\r\ndv auth login github --method token --token ghp_xxx\r\ndv auth login atlassian --user you@example.com --token <api-token>\r\nDV_TOKEN=ghp_xxx dv auth token github\r\n```\r\n\r\nScripts are unaffected: with no terminal attached the menu never appears and the\r\nrecommended method is used, so nothing that worked before needs a new flag.\r\n\r\nGitHub's device flow is the nicest of these: nothing listens on a port, there is no\r\nredirect URI to register, and it works unchanged over SSH.\r\n\r\n```bash\r\ndv auth login github     # shows a code, you paste it in the browser\r\ndv auth login google     # opens a browser, listens on 127.0.0.1\r\ndv auth token vercel --token <token>\r\ndv auth status           # what is connected, and for how long\r\n```\r\n\r\n### Bring your own OAuth app\r\n\r\nThere is no shared client id baked in — you register your own app, so the tokens are\r\nyours and the rate limits are yours.\r\n\r\n```bash\r\ndv config set github.clientId Iv1.xxxxx          # enable \"Device flow\" on the app\r\ndv config set google.clientId xxx.apps.googleusercontent.com\r\ndv config set linkedin.clientSecret xxxxx        # LinkedIn requires this\r\ndv config list                                    # shows the env var for each\r\n```\r\n\r\nEvery key also reads from an environment variable (`DV_GITHUB_CLIENT_ID`, …), which\r\ntakes precedence — useful in CI where nothing should be written to disk.\r\n\r\nFor Google and LinkedIn, allow `http://127.0.0.1` as a redirect URI on the app. The\r\nport is chosen at runtime, and Google accepts any port on the loopback address.\r\n\r\n---\r\n\r\n## Workflows\r\n\r\nThe reason this exists rather than being three thin API wrappers. Individual API calls\r\nare easy; the *sequence* is not, because it spans git, GitHub, Vercel and the running\r\nsite, and each has a different idea of what \"done\" means.\r\n\r\n```bash\r\ndv ship\r\n```\r\n\r\n```\r\nDevOS Ship\r\n────────────────────────────\r\n\r\n  ok Git status clean   main at 25f83ac\r\n  -  Tests passed       no test script\r\n  ok Build successful   99.4s\r\n  ok Pushed to origin   1 commit\r\n  ok Pull request open  #142 -> main\r\n  ok CI passed          3 checks\r\n  ok Deployment ready   https://myapp.vercel.app\r\n  ok Health check       200 https://myapp.vercel.app\r\n\r\nPull request  https://github.com/you/myapp/pull/142\r\nDeployment    https://myapp.vercel.app\r\n\r\n✓ Ship: SUCCESS\r\n```\r\n\r\nEach step skips itself with a reason when it does not apply — no test script, no\r\nGitHub remote, not linked to Vercel — so it degrades instead of failing.\r\n\r\n```bash\r\ndv ship --dry-run                 # show the plan, run nothing\r\ndv ship --skip-tests --no-pr      # turn steps off\r\ndv ship --project my-app          # name the Vercel project\r\ndv ship --json                    # machine-readable result\r\n```\r\n\r\nThe waits are the hard part, and are handled explicitly:\r\n\r\n- **CI** — GitHub reports no check runs for a few seconds after a push, which reads\r\n  identically to \"all passed\". The first 20 seconds of emptiness is treated as *still\r\n  starting*.\r\n- **Deployment** — Vercel builds from the git push, not from this CLI, so the step finds\r\n  the deployment matching your commit SHA rather than creating one.\r\n- **Health** — a deployment reporting READY means the build finished, not that the site\r\n  serves. The URL is actually requested.\r\n\r\n## What it does\r\n\r\n### GitHub\r\n\r\n```bash\r\ndv gh repo list --limit 50 --private\r\ndv gh repo view my-project\r\ndv gh repo create my-app --description \"...\" --clone      # private by default\r\ndv gh repo create my-app --public                         # asks first\r\ndv gh file ls my-app src\r\ndv gh file read my-app src/index.ts > local.ts\r\ndv gh search createKiln --repo my-app\r\ndv gh issue list my-app --state all --label bug\r\ndv gh issue create my-app --title \"Crash on save\" --body \"...\" --label bug\r\ndv gh pr create my-app --head feature/x --title \"Add x\"\r\ndv gh branch create my-app feature/x\r\ndv gh file put my-app README.md --file ./README.md -m \"docs: update\"\r\ndv gh run list my-app\r\ndv gh workflow run my-app -w release.yml --ref main\r\ndv gh repo clone my-app\r\n```\r\n\r\nA bare repo name is completed with your own login, so `dv gh issue list my-app` works;\r\n`owner/repo` is accepted anywhere too.\r\n\r\n### Gmail and Calendar\r\n\r\n```bash\r\ndv gmail search                        # defaults to your inbox\r\ndv gmail search \"from:github is:unread\"\r\ndv gmail read <id>\r\ndv gmail send --to a@b.com --subject \"Hi\" --body \"Hello\"   # asks first\r\ndv cal events --days 14\r\ndv cal free --min 45                   # gaps in your working hours\r\ndv cal add --title \"Review\" --start \"2026-08-20T15:00\" --minutes 45\r\n```\r\n\r\n### LinkedIn\r\n\r\n```bash\r\ndv li me\r\ndv li post --text \"Shipped the CLI today.\" --url https://example.com\r\n```\r\n\r\n`dv li me` returns name, email, locale and picture — that is the whole of what the\r\nstandard API tier exposes. Headline, connections, current position and your own posts\r\nneed a LinkedIn *partner* agreement, so the CLI says they are unavailable rather than\r\nfilling them in. `dv li posts` refuses outright instead of returning an empty list\r\nthat would read as \"you have no posts\".\r\n\r\n### Vercel\r\n\r\n```bash\r\ndv vercel project list\r\ndv vercel deploy list --project my-app\r\ndv vercel deploy redeploy my-app              # asks first\r\ndv vercel env list my-app                   # keys and targets; values are never fetched\r\n```\r\n\r\n### Anything else\r\n\r\n`dv api` makes every connector usable without a bespoke command for it:\r\n\r\n```bash\r\ndv api github /user\r\ndv api notion /v1/users\r\ndv api slack /conversations.list\r\ndv api stripe /v1/charges --query limit=5\r\ndv api linear /graphql --method POST --data '{\"query\":\"{ viewer { name } }\"}'\r\n```\r\n\r\nAnd a service this CLI has never heard of needs one line to define:\r\n\r\n```bash\r\ndv connect add pagerduty   --api-base https://api.pagerduty.com   --auth-style \"header:Authorization\"   --header \"Accept: application/vnd.pagerduty+json;version=2\"   --whoami /users/me\r\n\r\ndv auth token pagerduty --token <key>\r\ndv connect test pagerduty\r\ndv api pagerduty /incidents --query \"statuses[]=triggered\"\r\n```\r\n\r\n`--auth-style` covers how the credential attaches: `bearer`, `basic`, `token`,\r\n`header:<Name>` or `query:<param>`. That is the axis most CLIs hardcode, and the\r\nreason they only work with the services their author used.\r\n\r\nFor a service hosted per customer, set its base URL:\r\n\r\n```bash\r\ndv config set atlassian.apiBase https://your-site.atlassian.net\r\n```\r\n\r\n---\r\n\r\n## Scripting\r\n\r\nEverything supports `--json`, and **data goes to stdout while progress goes to\r\nstderr** — so a redirect produces a clean file even while status lines are printing:\r\n\r\n```bash\r\ndv gh repo list --json > repos.json\r\ndv gmail search \"is:unread\" --json | jq '.[] | .subject'\r\n```\r\n\r\nExit codes are meaningful, so a wrapper can tell the cases apart:\r\n\r\n| Code | Meaning |\r\n|---|---|\r\n| 0 | Fine |\r\n| 1 | Something failed |\r\n| 2 | The command line was wrong |\r\n| 3 | Not signed in, or the credential is dead — run `dv auth login` |\r\n| 4 | The provider refused: rate limit, missing scope, permission denied |\r\n| 5 | Nothing matched |\r\n| 6 | You declined a confirmation — nothing happened |\r\n\r\n```bash\r\ndv gh repo maybe-missing >/dev/null 2>&1\r\ncase $? in\r\n  0) echo \"exists\" ;;\r\n  3) dv auth login github ;;\r\n  5) echo \"no such repo\" ;;\r\nesac\r\n```\r\n\r\nAnything that writes, sends, publishes or deletes asks first. Pass `--yes` in scripts.\r\nDeleting a repository is the exception: it makes you type the full name, because a\r\n`y/N` prompt is too easy to fat-finger for something irreversible.\r\n\r\n---\r\n\r\n## Where your tokens live\r\n\r\n`~/.dv/credentials.json`, written `0600` (owner read/write only) via a temp file\r\nand an atomic rename, so a process killed mid-write cannot leave a truncated\r\ncredential file. `dv auth status` warns if the permissions ever loosen.\r\n\r\nTokens are stored **in plaintext**, protected by file permissions rather than\r\nencryption — the same model as `gh`, `aws` and `gcloud`, chosen because a keychain\r\nbinding means a native dependency on every platform. Anything running as you can read\r\nthem. On Windows there are no POSIX permission bits, so the permission check is skipped\r\nrather than printing a warning you could not act on; the file inherits your profile's\r\nACL, which restricts it to your account, SYSTEM and Administrators.\r\n\r\nA value captured so that `dv undo` can restore it — the contents of a deleted\r\nenvironment variable, for instance — goes to `~/.dv/undo-secrets.json` (also `0600`,\r\nexpiring after 14 days) and **never** into the journal. The journal is meant to be read\r\nand pasted around; it holds an opaque handle instead.\r\n\r\nClient ids live separately in `~/.dv/config.json` — a client id is a public\r\nidentifier, a token is not, and they should not share a file.\r\n\r\nSet `DV_HOME` to relocate both.\r\n\r\nSecrets are never echoed. `dv config get github.clientSecret` prints a mask, and\r\n`config set` confirms with a mask rather than the value you just typed.\r\n\r\nTokens are renewed **before** they expire, with a five-minute margin — a request that\r\nstarts with seconds left can still arrive after the token has died. Concurrent\r\ncommands share a single refresh rather than each spending the refresh token.\r\n\r\n---\r\n\r\n## Development\r\n\r\n```bash\r\nnpm install\r\nnpm run dev -- gh repos     # run from source\r\nnpm test                    # 243 tests\r\nnpm run build\r\n```\r\n\r\n`DV_DEBUG=1` prints stack traces. `NO_COLOR=1` disables colour, as does piping.\r\n\r\n### A Git Bash note\r\n\r\nMSYS rewrites arguments that look like absolute POSIX paths, so on Git Bash\r\n`dv api github /user` arrives as `C:/Program Files/Git/user`. `dv` detects and undoes\r\nthis using `EXEPATH`, so it just works. If you hit it in another tool, `//user` or\r\n`MSYS_NO_PATHCONV=1` are the usual workarounds.\r\n","readmeFilename":"README.md","_rev":"1-a1dea1c000638cbba6836a40feb1a923"}