Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ jobs:

- run: npm ci
- run: npm run typecheck
- run: npm run lint
- run: npm run test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist
.omc
*.log
*.tgz
.claude
54 changes: 54 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# @yavydev/cli

CLI for searching, managing, and configuring AI-ready documentation on Yavy.

## Commands

```bash
npm install # Install dependencies
npm run build # Build with tsup
npm run dev # Build in watch mode
npm run check # Run all checks (typecheck + lint + format + test)
npm run typecheck # TypeScript strict type checking
npm run lint # ESLint with typescript-eslint
npm run lint:fix # ESLint auto-fix
npm run test # Run tests (vitest)
npm run format:check # Check formatting (prettier)
npm run format # Fix formatting
```

## Architecture

The CLI is built on Commander.js with a modular command structure. Each command is a factory function returning a `Command` instance, registered in the entry point.

- **Commands** - one directory or file per command group. Each exports a factory.
- **API Client** - token-based HTTP client with retry logic and exponential backoff.
- **Auth** - OAuth2 PKCE flow with local callback server; credentials stored in `~/.yavy/`.
- **Prompts** - interactive flows using @clack/prompts for multi-select and @inquirer/prompts for input/select.

See [docs/architecture.md](docs/architecture.md) for details.

## Key Design Decisions

- `@/` path aliases throughout (configured in tsconfig, tsup, vitest).
- Strict TypeScript: `strict`, `noUncheckedIndexedAccess`, `noUnusedLocals`, `noUnusedParameters`, `noFallthroughCasesInSwitch`.
- ESLint with typescript-eslint: `consistent-type-imports`, `no-floating-promises`, `no-explicit-any`.
- Commands set `process.exitCode` instead of calling `process.exit()` directly - keeps code testable.
- Two auth patterns coexist: OAuth (login flow) and token-based (API token via env or config file).
- Interactive mode activates when required CLI flags are missing; flags always take precedence.

## After Changing Commands

- Register new commands in the entry point.
- Add the command to README.md under the Commands section.
- If adding a new command group, create a directory under `src/commands/`.

## After Changing the API Client

- Update tests that mock fetch globally.
- If adding new response types, define them alongside existing API interfaces in the client module.

## Documentation

- [Architecture](docs/architecture.md) - layers, data flow, design decisions
- [README](README.md) - install, quick start, command reference
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ Lists all projects you have access to across your organizations.
| -------- | -------------- |
| `--json` | Output as JSON |

### `yavy project create`

Create a new documentation project. Runs interactively when `--url` or `--github` is omitted.

| Flag | Description |
| ----------------- | ---------------------------------------- |
| `--url <url>` | Documentation URL (web crawl source) |
| `--github <repo>` | GitHub repository (e.g. laravel/docs) |
| `--org <slug>` | Organization slug |
| `--name <name>` | Project name (auto-generated if omitted) |
| `--public` | Make project public (default) |
| `--private` | Make project private |
| `--branch <name>` | GitHub branch override |
| `--docs-path <p>` | GitHub docs path |
| `--no-sync` | Skip initial auto-sync |

### `yavy generate <org/project>`

Downloads a skill from a project's indexed documentation.
Expand Down
61 changes: 61 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Architecture

How the CLI is structured, where things live, and why.

## Layers

### Entry Point

`bin/yavy.js` is the shebang entry that loads the built output. The source entry registers all commands with Commander.js and calls `parseAsync()`. Top-level error handler catches unhandled rejections and exits with code 1.

### Commands

Each command is a factory function that returns a configured `Command`. Commands live in `src/commands/` - either as single files (login, logout, search) or directories when they have submodules (init, project).

Command factories wire up flags, descriptions, and an async action handler. The action handler orchestrates: validate inputs, call API, format output. No business logic lives in the action - it delegates to dedicated modules.

### API Client

Single HTTP client class handles all Yavy API communication. Key behaviors:

- Bearer token auth (loaded from credential store or environment)
- Retry with exponential backoff on 429/502/503/504
- Request timeout with AbortController
- Typed response parsing

### Authentication

Two paths:

1. **OAuth PKCE** - `yavy login` opens browser, spawns local HTTP server for callback, exchanges code for token. Credentials persisted to `~/.yavy/credentials.json` with 0600 permissions. Auto-refresh when token nears expiry.
2. **Token-based** - `YAVY_API_TOKEN` env variable or `~/.yavy/config.json`. Used by the project creation flow and CI environments.

### Interactive Prompts

When required CLI flags are missing, commands fall back to interactive mode. Prompts collect the missing values, then merge with any flags that were provided. Two prompt libraries are in use: @clack/prompts (multi-select, spinners) and @inquirer/prompts (input, select).

### Utilities

- **Output** - colored terminal helpers (success, error, warn, info) using chalk
- **Paths** - skill output directories, zip-slip prevention, safe directory creation
- **Errors** - API error formatting that maps HTTP status codes to actionable messages

## Data Flow

```
User runs command
-> Commander parses flags
-> Command action handler runs
-> If missing flags: interactive prompts fill them in
-> API client makes request (with retry)
-> Response formatted and printed to stdout
-> Errors caught, formatted, printed to stderr
```

## Build & Distribution

tsup bundles to ESM targeting Node 20+. The dist includes declarations and source maps. Published to npm as `@yavydev/cli` with `dist/` and `bin/` in the package.

## Testing Strategy

Vitest with globals enabled. Tests mock at module boundaries - the API client, prompts, and filesystem are mocked; pure functions (payload builders, error formatters, org extractors) are tested directly. Console output is verified via spies on console.log/console.error.
24 changes: 24 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-floating-promises': 'error',
},
},
{
ignores: ['dist/', 'node_modules/', '*.config.*'],
},
);
Loading
Loading