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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
- run: npm install --no-audit --no-fund
- run: npm run build
- run: npm run lint --if-present
- run: npm test
- run: npm run test:coverage
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
NPM_CONFIG_PROVENANCE: true
HUSKY: 0
run: npx semantic-release
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,21 @@ dist/
.DS_Store
npm-debug.log*

# Local npm cache used by pack:dry
.npm-cache/

# Coverage reports
coverage/

# Editor settings (keep local)
.vscode/
.idea/

# Local test artifacts
*.tgz

# Project-local override files (avoid accidental commits)
.openrouterrc
.openrouterrc.json
.openrouterrc.yaml
.openrouterrc.yml
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"cSpell.words": [
"analyzer",
"behavior",
"Commitizen",
"commitlint",
"letuscode",
Expand Down
37 changes: 37 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Repository Guidelines

## Project Structure & Module Organization
- `src/` TypeScript source (ESM). Entry: `src/index.ts` → `main.ts`; shared helpers in `src/shared/`.
- `bin/` CLI launcher (`openrouter`) that loads `dist/index.js`.
- `dist/` build output (generated by `npm run build`). Do not edit.
- `tests/` Vitest specs (e.g., `config.spec.ts`, `openrouter.spec.ts`).
- `docs/` design and configuration notes; `scripts/` release/build helpers.
- Config files: global `~/.config/openrouter-cli/config.json`; project overrides `.openrouterrc(.json|.yaml|.yml)`.

## Build, Test, and Development Commands
- `npm run dev` — run CLI in TS directly (ts-node ESM). Example: `npm run dev -- ask "Hello"`.
- `npm run build` — compile TypeScript to `dist/`.
- `npm test` / `npm run test:watch` — run Vitest once / in watch mode.
- `npm run lint` — ESLint over `src/**`.
- `npm run commit` — Commitizen prompt for Conventional Commits.
- After build: `openrouter --help` (via `bin/openrouter`) or `node dist/index.js`.

## Coding Style & Naming Conventions
- TypeScript, ESM, 2-space indentation, single quotes allowed; prefer explicit return types for exported functions.
- File names: lowercase (e.g., `main.ts`, `repl.ts`, `shared/openrouter.ts`).
- ESM import paths in TS include `.js` (e.g., `import { x } from './main.js'`).
- Use helpers to protect secrets; never log API keys. Config files are written with chmod `600` when possible.
- Linting via flat-config ESLint (`eslint.config.js`); pre-commit runs `lint-staged`.

## Testing Guidelines
- Framework: Vitest. Place tests in `tests/*.spec.ts` with clear, isolated cases.
- Cover config precedence, URL joining, and CLI option parsing. Example: see `tests/openrouter.spec.ts` for `joinUrl`.
- Run `npm test` locally; keep tests deterministic (no network). Mock I/O when needed.

## Commit & Pull Request Guidelines
- Follow Conventional Commits: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `test:`; use scopes when helpful (e.g., `feat(config): ...`). Use `npm run commit`.
- PRs: concise description, linked issues, CLI examples (commands/output), and updated docs when behavior changes. Ensure `lint`, `test`, and `build` pass.

## Security & Configuration Tips
- Prefer `OPENROUTER_API_KEY`/`OPENAI_API_KEY`; avoid committing secrets. Use `openrouter config --api-key` only if persistence is required.
- Example: `openrouter config --model meta-llama/llama-3.1-8b-instruct --domain https://openrouter.ai/api/v1`.
126 changes: 43 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,88 +1,48 @@
# openrouter-cli

[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://www.conventionalcommits.org)
[![semantic-release](https://img.shields.io/badge/semantic--release-automated-green?logo=semantic-release)](https://semantic-release.gitbook.io)
[![npm (beta)](https://img.shields.io/npm/v/%40letuscode%2Fopenrouter-cli/beta)](https://www.npmjs.com/package/@letuscode/openrouter-cli)

Releases are automated: Conventional Commits determine version bumps and semantic-release publishes stable builds from `main` and prereleases on the `beta` channel from dev branches. Use `npm run commit` for a guided, standards-compliant commit message.

CLI tool for OpenRouter (OpenAI-compatible). Early v0.1.0 skeleton.

Installation
- npm: `npm i -g @letuscode/openrouter-cli` (or run locally with `npx @letuscode/openrouter-cli`)

Setup
- Set API key via env: `export OPENROUTER_API_KEY=...` (or `OPENAI_API_KEY`)
- Or persist it: `openrouter config --api-key sk-...`
- Full guide: see `docs/CONFIGURATION.md`

Defaults
- Domain: `https://openrouter.ai/api/v1`
- Model: `meta-llama/llama-3.1-8b-instruct`

Commands
- `openrouter config [--domain URL] [--model NAME] [--api-key KEY] [--profile NAME] [--list]`
- `openrouter test [--profile NAME]` — calls `/models` to verify connectivity
- `openrouter ask "your question" [-m MODEL] [-s SYSTEM] [--profile NAME] [--no-stream]`
- `openrouter repl [--profile NAME]` — interactive chat (streaming). Commands: `exit`, `/model`, `/system`

Config
- Stored at `~/.config/openrouter-cli/config.json` with chmod 600 when possible.
- API keys are never logged; printed configs redact the key.

Project-local overrides (.openrouterrc)
- Place a `.openrouterrc` in the project root (JSON or YAML) to override `domain` and `model` for that project.
- Example JSON:
{
"domain": "http://localhost:11434/v1",
"model": "gemma2:9b-instruct"
}

Profiles
- Store named profiles in the global config under `profiles`.
- Select a profile at runtime via `--profile <name>`.
- Update/create a profile via: `openrouter config --profile dev --domain ... --model ...`.
- More details: see `docs/CONFIGURATION.md`

Publishing
Stable releases (main)
- Conventional Commits drive versioning via semantic-release.
- Merge to `main` with `feat`, `fix`, or `BREAKING CHANGE` commits triggers release:
- Publishes to npm with the next semver.
- Creates Git tag (`vX.Y.Z`) and GitHub Release.
- Note: CHANGELOG.md is generated in CI but not committed back (tags/releases are the source of truth).

Pre-releases
- Beta snapshots publish from dev branches (`story/**`, `feature/**`, `release/**`).
- Install beta builds: `npm i @letuscode/openrouter-cli@beta`
- Driven by commit messages; semver prerelease scheme.
- Beta builds are npm-only; stable tags are created on `main`.

Contributing
- Commit style: Conventional Commits (e.g., `feat: add repl /help`, `fix: handle URL join`) drive releases.
- Local hooks (Husky):
- commit-msg: commitlint enforces Conventional Commits.
- pre-commit: lint-staged runs ESLint with `--fix` on staged files.
- pre-push: runs `npm test` and `npm run build`.
- Setup: run `npm install` once to install hooks (postinstall runs `husky install`).
- Manual runs:
- `npx commitlint --from HEAD~1 --to HEAD` (check last commit)
- `npx lint-staged` (run staged linting)
- `npm run lint && npm test && npm run build`

Conventional Commits examples
- `feat: add config command to update model`
- `fix: preserve /api/v1 path when joining URLs`
- `docs: add contributing section`
- `chore: set up semantic-release`
- `refactor: extract URL join helper`
- `perf: speed up SSE parsing`
- `test: add URL join tests`
- `feat!: change default model` with a footer:
- `BREAKING CHANGE: default model is now meta-llama/llama-3.1-8b-instruct`

Guided commit (Commitizen)
- Run `npm run commit` for an interactive prompt that produces a valid Conventional Commit message.
OpenAI‑compatible CLI for OpenRouter. Ask questions, run a REPL, and manage per‑project or global settings.

Requirements
- Node.js 18.17+ (ESM)

Install
- Global: `npm i -g @letuscode/openrouter-cli`
- One‑off: `npx @letuscode/openrouter-cli --help`

Quick start
1) Run the wizard: `openrouter init` (select provider, set domain/model, and add an API key)
2) Ask something: `openrouter ask --no-stream "Hello!"`
3) Chat interactively: `openrouter repl`

Core commands
- `openrouter init` — interactive setup (provider, domain, key, model, profile)
- `openrouter config` — view config or set API key
- Examples:
- `openrouter config --list`
- `openrouter config --api-key sk-...` (stores in base config)
- `openrouter config --profile dev --api-key sk-...` (stores in profile)
- `openrouter test` — verify connectivity (`/models`)
- `openrouter test [--profile dev] [--no-init]`
- `openrouter ask` — one‑shot prompt
- `openrouter ask "your question" [-s SYSTEM] [--format auto|plain|md] [--profile NAME] [--no-stream] [--no-init]`
- `openrouter repl` — interactive chat
- REPL commands: `exit`, `/model <name>`, `/system <text>`, `/format md|plain`, `/stream on|off`

Configuration
- API key via env (recommended): `export OPENROUTER_API_KEY=...` (or `OPENAI_API_KEY`)
- Global config file: `~/.config/openrouter-cli/config.json` (chmod 600 where possible; keys never logged)
- Project overrides: add `.openrouterrc` (JSON or YAML) in your project root
- Example `.openrouterrc` (JSON):
{
"domain": "http://localhost:11434/v1",
"model": "gemma2:9b-instruct"
}
- Changing default provider, domain, or model: re‑run `openrouter init` (this is the only way to update these defaults).
- More details: see `docs/CONFIGURATION.md`.

Troubleshooting
- “Missing API key”: set `OPENROUTER_API_KEY` or run `openrouter init` (or `openrouter config --api-key sk-...`).
- Non‑TTY/CI: pass `--no-init` to skip interactive prompts.

License
- MIT
Empty file modified bin/openrouter
100644 → 100755
Empty file.
24 changes: 17 additions & 7 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Defaults:
- Domain: `https://openrouter.ai/api/v1`
- Model: `meta-llama/llama-3.1-8b-instruct`

Interactive onboarding:
- Run `openrouter init` to select a provider preset, set domain/model, and (optionally) persist an API key. Commands like `ask`, `test`, and `repl` auto‑prompt on missing keys in TTY unless `--no-init` is provided.

## Global Config

- Path: `~/.config/openrouter-cli/config.json`
Expand All @@ -38,16 +41,16 @@ Defaults:

### Managing Global Config via CLI

- Update base (global) config:
- `openrouter config --domain https://openrouter.ai/api/v1 --model meta-llama/llama-3.1-8b-instruct`
- Create/update a profile:
- `openrouter config --profile dev --domain http://localhost:11434/v1 --model gemma2:9b-instruct`
- Persist an API key (optional):
- Global: `openrouter config --api-key sk-...`
- Profile: `openrouter config --profile dev --api-key sk-...`
- Inspect config and profiles (redacted):
- `openrouter config --list`

### Changing Provider, Domain, or Model

- Use `openrouter init` to choose or change provider, domain, and default model. Re‑run `init` any time to update these defaults.

## Project‑Local Overrides (.openrouterrc)

Place a `.openrouterrc` in your project root to set project‑specific defaults. This file should not contain secrets.
Expand Down Expand Up @@ -84,14 +87,17 @@ Examples:

CI/CD: store the key as a secret and inject it into the job environment.

Auto‑init behavior
- In interactive terminals, `openrouter ask|test|repl` will trigger `openrouter init` if no API key is available. Pass `--no-init` to skip.

## CLI Flags (Per‑Command)

- Pick a profile for a command:
- `openrouter test --profile dev`
- `openrouter ask --profile dev "Hello world"`
- `openrouter repl --profile dev`
- Override model/domain for a single invocation:
- `openrouter ask -m other-model "Hello"`
Note: To change the default domain or model, re‑run `openrouter init`. Per‑invocation model overrides have been removed to keep usage simple.

## Putting It Together: Precedence

Expand Down Expand Up @@ -129,6 +135,10 @@ Example resolution for `openrouter ask --profile dev "Hi"` in a project with `.o
- `openrouter ask --profile dev "Hello"`
- Project override file:
- `./.openrouterrc` (JSON/YAML)
- Tip: This is optional and best for users running the CLI inside a project directory. For global one‑off use, rely on `openrouter init` defaults.
- API key env vars:
- `OPENROUTER_API_KEY` (preferred), `OPENAI_API_KEY`

- Run the wizard:
- `openrouter init`
- Apply a provider preset:
- `openrouter config --provider openrouter|openai|custom`
26 changes: 26 additions & 0 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Contributing

This project uses Conventional Commits and semantic‑release. Releases are automated from `main`; prereleases publish from feature branches.

Basics
- Write descriptive, minimal PRs that include a brief summary, usage examples, and updated docs when behavior changes.
- Follow Conventional Commits (e.g., `feat: ...`, `fix: ...`, `docs: ...`, `refactor: ...`, `test: ...`). Use scopes when helpful: `feat(config): ...`.

Local workflow
- Install deps: `npm install`
- Lint: `npm run lint`
- Test: `npm test`
- Build: `npm run build`
- Guided commit: `npm run commit`
- Optional hooks: `npm run dev:hooks` to install Husky locally
- commit‑msg: commitlint
- pre‑commit: lint‑staged (ESLint fix on staged files)
- pre‑push: tests + build

Tooling tips
- If tests fail in a restricted shell due to worker/thread sandboxing, run them in CI or a normal local shell (where Vitest can spawn workers). CI is the source of truth.
- If `npm pack --dry-run` fails due to cache permissions, use the local cache script: `npm run pack:dry` (sets `--cache ./.npm-cache`). To fix your user cache permanently, run: `sudo chown -R $(id -u):$(id -g) ~/.npm`.

CI/Release
- CI runs build, lint, and tests on Node 18.x and 20.x.
- Release workflow uses semantic‑release with provenance enabled.
34 changes: 19 additions & 15 deletions docs/DECISION.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,35 +123,39 @@ This document tracks major design choices, scope, and phased roadmap for the **o
- ESLint on staged files pre-commit; tests pre-push.


## Iteration 2 (v0.2.0) — Plan
## Iteration 2 (v0.2.0) — Status

### Scope
- Project-local `.openrouterrc` overrides.
- Profiles (e.g. `--profile dev`) with per-profile domain/model/system overrides.
- Project-local `.openrouterrc` overrides (JSON and YAML).
- Profiles (e.g. `--profile dev`) with per-profile overrides.
- Init-driven defaults: provider/domain/model set via `openrouter init` (re-run to change).

### Design
- Config precedence:
- CLI flags > env vars > project `.openrouterrc` > global config.
- Config precedence (effective):
- CLI flags (e.g., `--profile`, output/stream options) > env vars (API key) > project `.openrouterrc` (JSON/YAML) > global config > built-in defaults.
- Files:
- Global: `~/.config/openrouter-cli/config.json` (existing)
- Project: `./.openrouterrc` (JSON or YAML; v0.2: JSON only)
- Global: `~/.config/openrouter-cli/config.json` (stores base values, optional profiles)
- Project: `./.openrouterrc` or `.openrouterrc.{json,yaml,yml}` (overrides domain/model per project)
- Profiles:
- Specify via `--profile <name>` on CLI and REPL.
- Global and project configs can define `profiles` object; missing keys fall back to base config.
- Select via `--profile <name>` on CLI and REPL.
- Global config can define `profiles` object; missing keys fall back to base config.

### CLI/UX
- Add `--profile` to `ask`, `repl`, and `test`.
- `config` command: support reading/writing base values only in v0.2; profiles are read-only initially.
- `--profile` available on `ask`, `repl`, and `test`.
- `init` is the only way to change provider/domain/model (interactive wizard; auto-prompts in TTY when key is missing unless `--no-init`).
- `config` is narrowed to listing and API key persistence (base or profile); domain/model/provider flags removed.
- Removed per-invocation model override (`ask -m`).

### Security
- Same key-handling guarantees; never log secrets.
- Config file written with chmod 600 where possible; keys redacted in `--list` output.
- Project file is not created automatically; only read if present.

### Tasks
- Implement loader for `.openrouterrc` with merge logic and precedence. ✅ Completed
- Implement loader for `.openrouterrc` (JSON/YAML) with merge logic and precedence. ✅ Completed
- Add profile resolver with fallback. ✅ Completed
- Update help/docs and examples. ✅ Completed
- Add unit tests for precedence and profiles. ⏳ Partial (URL join test added; profile tests next)
- Update help/docs and examples (consumer-focused README, CONFIGURATION). ✅ Completed
- Add tests: URL join; YAML rc precedence; config redaction; streaming SSE parsing; ask error handling; CLI help shape. ✅ Completed

### Out of Scope for v0.2
- YAML/TOML formats, keychain storage, remote profiles.
- TOML formats, keychain storage, remote profiles, tool plugins/MCP phases.
39 changes: 39 additions & 0 deletions docs/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Next TODOs (Iteration Planning)

## Top Priority — CLI Visual Polish
- [ ] Discuss desired look/feel (Claude/Codex‑style): banner/logo, colors, spacing.
- [ ] Pick libraries: `chalk` (colors), `boxen` (frames), `gradient-string` (optional), `ora` (spinners), `table`/`cli-table3` (tabular output).
- [ ] Optional image/avatar in terminal (TTY only): evaluate `terminal-image` with fallback to ASCII art.
- [ ] Apply consistent style to `--help`, `ask` (non‑stream output), `repl` prompts and tips.
- [ ] Accessibility: color‑safe palette, no-color fallback via `NO_COLOR`/TTY detection.

## Tests & Coverage
- [ ] Add coverage thresholds in Vitest (start: 70% lines/branches; raise gradually).
- [ ] Add tests:
- [ ] ask (non‑stream) markdown snapshot (ANSI stripped before assert).
- [ ] Auto‑init skip in non‑TTY (mock isTTY and stub wizard).
- [ ] testConnection error mapping (HTTP 4xx/5xx → friendly messages).
- [ ] REPL non‑stream render path snapshot (strip ANSI).
- [ ] Optional: CLI E2E smoke (pack → run `--help`) in CI artifact.

## Error Handling & Timeouts
- [ ] Add request timeout to fetch and surface timeouts clearly.
- [ ] Normalize error output (network/DNS/401/429) with concise guidance.

## Init Flow Polish
- [ ] Improve prompts copy; add final summary/confirm before save.
- [ ] Consider non‑interactive flags for automation: `init --provider --domain --model --api-key --profile`.
- [ ] Keep domain/model changes only via `init` (docs reflect this).

## Docs
- [ ] README: add short, copy‑paste examples (ask md render, brief REPL transcript).
- [ ] Consider a small GIF/screencast once UI polish lands.

## CI & Packaging
- [ ] Upload coverage (e.g., Codecov) [optional].
- [ ] Attach `npm pack --dry-run` tarball as CI artifact for review.

## Open Questions (to discuss)
- [ ] Keep `.openrouterrc` project overrides long‑term or simplify later?
- [ ] Image/avatar in terminal: ship ASCII art only, or inline images when supported?
- [ ] Non‑interactive `init` flags: needed now for CI/scripts?
Loading