Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Cli Auth

ASecurity

How to use @owlmeans/cli-auth — Node-side browser sign-in for a command-line tool or stdio MCP server: the `~/.owlmeans` dotenv credentials file and its precedence, atomic 0600 writes, the browser opener and its suppression, the cross-process sign-in lock, and makeCliCredentials (token/require/invalidate/signOut) that runs the device grant. Auto-invoked when adding sign-in to a CLI, reading or writing the credentials file, diagnosing "signed in but the token is ignored", or writing tests that...

3 stars
0 votes
0 copies
0 views
Added 9/22/2026
devopsgoreactnodetestingapi

Works with

cliapimcp

Security Analysis

A100/100

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add owlmeans/common --skill cli-auth --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cli Auth?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Cli Auth
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/owlmeans-cli-auth-common/badge)](https://www.skillsdirectory.com/skills/owlmeans-cli-auth-common)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: cli-auth
description: How to use @owlmeans/cli-auth — Node-side browser sign-in for a command-line tool or stdio MCP server: the `~/.owlmeans` dotenv credentials file and its precedence, atomic 0600 writes, the browser opener and its suppression, the cross-process sign-in lock, and makeCliCredentials (token/require/invalidate/signOut) that runs the device grant. Auto-invoked when adding sign-in to a CLI, reading or writing the credentials file, diagnosing "signed in but the token is ignored", or writing tests that must not touch the developer's real credentials.
user-invocable: false
---
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->

# @owlmeans/cli-auth

**Layer:** Tooling (Node/Bun — no React, no DOM, no framework runtime)
**Install:** `"@owlmeans/cli-auth": "^0.1.18-rc.2"` in `dependencies`
**Contracts:** `@owlmeans/oauth` (discovery, device authorization, polling, revoke, `SignInRequired`)
**Server half:** any API built on `@owlmeans/server-oauth`; **consumers:** `@owlmeans/viable-mcp`

## Key Exports

| Export | Description |
|--------|-------------|
| `makeCliCredentials(opts)` | The holder: `token()` · `require(waitMs?)` · `invalidate(rejected)` · `signOut()` |
| `resolveEnvFile(env?)` | `OWLMEANS_CREDENTIALS`, else `~/.owlmeans` |
| `parseEnv(content)` | `KEY=value`, optional `export `, `#` comments, one level of quotes |
| `readCredentialsFile(env?)` | The file's values alone — what a token is *bound* to |
| `loadOwlmeansEnv(env?)` | File overlaid by the process environment (below) |
| `setEnvValues(path, values)` | Replace named keys, keep every other line; atomic, `0600`; returns `{ insecurePermissions }` |
| `openBrowser(url, env?)` | Best-effort detached opener; `false` on any refusal |
| `claimOrJoinLock` · `readLock` · `releaseLock` · `lockPathFor` · `SignInLockInfo` | The sign-in lock |
| `ENV_CREDENTIALS_FILE` · `DEFAULT_CREDENTIALS_FILENAME` · `DEFAULT_WAIT_MS` (20 s) · `MAX_SIGN_IN_WAIT_MS` (15 min) | Constants |

`CliCredentialsOptions`: `apiUrl`, `clientId` (a static client the server declared, or a CIMD URL),
`tokenEnvKey`, `apiUrlEnvKey` (both required — the package is not tied to one product's key
names), `deviceName?` (defaults to `host · user`), `resource?` (defaults to `apiUrl`), `scope?`,
`env?` (defaults to `process.env`), `onNotify?`.

## The credentials file

Dotenv-style `KEY=VALUE`, e.g. `VIABLE_API_URL=…` and `VIABLE_API_TOKEN=…`. Never merged with another
file; a CLI that serves several deployments points `OWLMEANS_CREDENTIALS` at one file each.

- **The environment wins over the file** (`loadOwlmeansEnv` → `{ ...file, ...envWithoutEmpty }`).
- **An empty environment value counts as unset.** A harness config that expands an unset variable
  (`${VIABLE_API_TOKEN:-}`) produces `''`, which must not shadow the file — "I did not set it" and
  "set it to nothing" are not the same, and the file is the more deliberate of the two.
- **A token belongs to the file's API URL.** `token()` refuses a file token when the file names a
  different `apiUrlEnvKey` value than the one asked for; a file that names none belongs to whichever
  URL is asked for. A token is never sent to another API.
- **Writes are atomic and private:** temp file in the same directory, `rename`, mode `0600`, other
  lines and comments preserved. Loose permissions on an existing file are *reported*
  (`insecurePermissions`), not silently tightened — someone may have set them on purpose.
- The token is never read from argv, never written to a harness/config file.

## The holder

- **`token()`** — the environment value, else the bound file value, else `null`.
- **`require(waitMs = DEFAULT_WAIT_MS)`** — returns a token, starting or joining ONE device sign-in
  per API URL: discover → `requestDeviceAuthorization` → claim/join the lock → `notify("Sign in at
  <url> with code <code>")` → open the browser (owner only) → `pollDeviceToken` → persist
  `{tokenEnvKey, apiUrlEnvKey}` → resolve. If `waitMs` runs out first it throws `signInRequired`
  (`url`, `code`, `expiresAt` from the lock) and **polling continues in the background**; the next
  `require()` joins it instead of starting over. `denied`/`expired`/`aborted` become
  `OAuthAccessDenied` / `OAuthError`.
- **The `waitMs` ceiling is a timer that is cleared** when the race is decided. A pending timer keeps
  a Node process alive, so an uncleared one made `viable-mcp login` (ceiling 15 minutes) hang after
  it had signed in. Any new timeout in this package clears its timer the same way.
- **Single-flight in-process:** `beginOrJoin` is synchronous up to the module-level
  `inFlightByApiUrl` map insert, so two racing `require()` calls converge. An `await` before that
  insert reopens the race (a `Promise<Promise<…>>` there once made `require()` hang forever).
- **Single-flight across processes:** `<credentials file>.lock` (mode `0600`) records `pid`,
  `apiUrl`, `verificationUri`, `userCode`, `deviceCode`, `interval`, `expiresAt` and a `nonce`. A live
  lock for the same URL (unexpired, pid alive) makes a second process a **joiner** — it shows the same
  code, does not open a second tab, and polls the same authorization. `releaseLock` deletes only a
  lock carrying the caller's own `nonce`. Best effort, not mutual exclusion: two processes racing the
  same instant may each drive a sign-in, costing a tab, never a corrupt file.
- **`invalidate(rejected)`** — call on a 401. A file token that is still the rejected one is removed
  (next `require()` signs in); an **environment** token is never silently replaced — it throws
  `TokenRejected(tokenEnvKey)` so the operator hears "VIABLE_API_TOKEN was refused" instead of the
  tool quietly becoming another identity.
- **`signOut()`** — best-effort `revokeToken` at the server, then remove the file token.

## Opening the browser

`openBrowser` spawns `open` / `xdg-open` / `cmd /c start` **detached with `stdio: 'ignore'`**, because
stdout may carry a protocol (an MCP server's JSON-RPC) that nothing the opened program writes may
reach. It refuses (returns `false`) when `OWLMEANS_NO_BROWSER=1` or `BROWSER=none`, and on Linux
with neither `DISPLAY` nor `WAYLAND_DISPLAY`. Automation and end-to-end runs set
`OWLMEANS_NO_BROWSER=1` and open the printed URL themselves. A failed open is never fatal — the
"sign in at … with code …" line is the real interface and works over SSH and in containers.

## Testing

Category A: `bun test ./tests` (`env-file`, `holder`, `lock`, `open-browser` specs). Rules that keep
the suite off the developer's machine:

- Point `OWLMEANS_CREDENTIALS` at a temp path — never let a test read the real `~/.owlmeans`.
- Holder tests use a **distinct `apiUrl` per test**: `inFlightByApiUrl` is module-level, so a shared
  URL leaks a pending sign-in from one test into the next.
- `holder.spec.ts` replaces `globalThis.fetch` with a fake device-flow server (metadata,
  `device_authorization` with `interval: 0.001`, a token endpoint that stays `authorization_pending`
  for N polls, revoke) and restores it in `afterEach`; never wait out a real 5 s interval.
- Anything that spawns a CLI passes `OWLMEANS_NO_BROWSER=1` and deletes the token and any URL
  override variables from the inherited environment.

Attribution

owlmeansowlmeans
View sourceMore from owlmeans →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Terraform Module Library

Build reusable Terraform modules for AWS, Azure, and GCP infrastructure following infrastructure-as-code best practices. Use when creating infrastructure modules, standardizing cloud provisioning, or implementing reusable IaC components.

397921 votes

sematext-otel

Wire a service's OpenTelemetry output to Sematext Cloud. Walks through region, App-type, instrumentation flow (managed OTLP endpoint vs Sematext Agent), and signal selection (traces/metrics/logs), then produces the exact env-var block and points at a runnable reference example in this repo. Invoke when instrumenting a new app for Sematext.

01 votes

Deployment Patterns

Deployment workflows, CI/CD pipeline patterns, Docker containerization, health checks, rollback strategies, and production readiness checklists for web applications. Use when setting up deployment infrastructure or planning releases.

2459130 votes

Babysit

Watch a pull request or review cycle until it is ready to merge. Use when asked to babysit, monitor, or keep checking PR comments, reviews, and CI until all actionable issues are resolved.

942310 votes

V7 Roster

Interact with the Paperclip control plane API for task coordination and governance. Use when checking assignments, updating issue status, posting comments, delegating work, managing routines, or calling Paperclip API endpoints.

805540 votes
View all in devops →