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 .claude/rules/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ HttpClient (axios) -> performs the real HTTP request to API_BASE_URL
4. **Pure transform layer**: `bm25.ts`, `openapi-to-commands.ts`, and `command-search.ts` perform no I/O; they take inputs and return outputs. This keeps them trivially unit-testable.
5. **Side effects at the edges**: filesystem in `config.ts`/`profile-store.ts`/`openapi-loader.ts`, network in `cli.ts` via `HttpClient`. Inject these via constructors (`fs`, `httpClient`) so tests can swap them.
6. **TypeScript strict**: `strict: true` in `tsconfig.json`. Explicit types for exported functions and public interfaces.
7. **No surprise breaking changes**: every CLI-visible change must be reflected in `README.md` and `CHANGELOG.md`.
7. **No surprise breaking changes**: every CLI-visible change must be reflected in `README.md`.

## Layers and allowed dependencies

Expand Down
7 changes: 3 additions & 4 deletions .claude/rules/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ When the user asks for a new feature (words like "feature", "add", "implement",
6. **Full suite**: `npm test`. All tests must be green. Fix regressions before moving on.
7. **Build check**: `npm run build` to confirm `tsc` is clean (no type errors).
8. **Docs**: update `README.md` whenever any of the following change: CLI flags, command names, profile fields, `.ocli/` layout, BM25 search behavior, supported OpenAPI/Swagger features, or the benchmark numbers. If you changed observable CLI output (`--help`, error messages, exit codes), update the relevant section of the README. The `examples/skill-ocli-api.md` and `skills/ocli-api/SKILL.md` must stay aligned with the documented agent workflow.
9. **Changelog**: add an entry to `CHANGELOG.md` for any user-visible change.
10. **Report**: brief summary of files touched, tests added, suite result.
9. **Report**: brief summary of files touched, tests added, suite result.

## Bug fixes (TDD)

Expand All @@ -30,14 +29,14 @@ When the user reports a bug (words like "bug", "fix", "ошибка", "баг",
5. **Confirm green**: re-run the regression test.
6. **Full suite**: `npm test`. All tests green.
7. **Build check**: `npm run build`.
8. **Docs**: update `README.md` if the bug affected documented behavior; add a `CHANGELOG.md` entry.
8. **Docs**: update `README.md` if the bug affected documented behavior.
9. **Report**: what was broken, what changed, suite result.

## Before the final answer

- `npm test` is green - **always**.
- `npm run build` is clean.
- `README.md` and `CHANGELOG.md` reflect any user-visible change.
- `README.md` reflects any user-visible change.
- Report what changed, which tests were added, and the suite result.

## Rules sync (Cursor <-> Claude)
Expand Down
105 changes: 70 additions & 35 deletions .cursor/rules/architecture.mdc
Original file line number Diff line number Diff line change
@@ -1,53 +1,88 @@
---
description: Architecture of openapi-to-cli (ocli)
globs: "src/**/*.ts"
alwaysApply: true
---

## Architectural Rules
# Architecture of openapi-to-cli (ocli)

### General Architecture
`ocli` is a TypeScript/Node CLI that converts OpenAPI/Swagger specs into runtime CLI commands. No code generation: at every invocation it loads a cached spec and builds command definitions on the fly.

The `openapi-to-cli` project is a Node.js/TypeScript CLI application that:
## Data flow

- reads an OpenAPI/Swagger spec from a URL or file;
- caches the spec in `.ocli/specs`;
- uses profiles to describe connected APIs;
- maps OpenAPI operations to `ocli` subcommands.

### Data Flow

```text
ocli onboard --options --> write profile (profiles.ini) --> download OpenAPI --> save to .ocli/specs
```
ocli profiles add <name> --api-base-url ... --openapi-spec ...
|
v
ConfigLocator -> .ocli/ dir (global or local)
|
v
ProfileStore -> profiles.ini (read/write/select)
|
v
OpenapiLoader -> fetches spec, caches under .ocli/specs/<profile>.json
|
v
OpenapiToCommands -> parses spec, applies include/exclude filters,
builds CliCommand[] (name, method, path, options, body schema)
|
v
ocli [--profile] <tool> [options] --> load profile + cached spec --> build commands from OpenAPI --> perform HTTP request to API_BASE_URL
CommandSearch (BM25) -> ranks commands by NL query or regex
|
v
cli.ts (yargs) -> resolves profile + spec, dispatches: profiles | use | commands | <toolName>
|
v
HttpClient (axios) -> performs the real HTTP request to API_BASE_URL
```

### Components
## Components (mapping to `src/`)

- `config.ts` - `ConfigLocator`. Finds `.ocli/` (global `~/.ocli/`, local in CWD), resolves `profiles.ini` paths.
- `profile-store.ts` - `ProfileStore`, `Profile`. Reads/writes `profiles.ini`, tracks current profile, validates fields.
- `openapi-loader.ts` - `OpenapiLoader`. Loads spec from URL or local file, caches it to `.ocli/specs/<profile>.json`, refreshes on demand. Resolves external `$ref` across multi-file specs.
- `openapi-to-commands.ts` - `OpenapiToCommands`, `CliCommand`, `CliCommandOption`. Walks the spec, applies include/exclude filters, expands path-level params, resolves local `$ref`, builds command names with optional prefix, expands `enum`/`default`/`nullable`/`oneOf` schema hints for `--help`.
- `command-search.ts` - `CommandSearch`. BM25 over `(name, method, path, description, options[].name)`, plus regex fallback. Same engine used by both `ocli commands` and any future agent skill.
- `bm25.ts` - tokenizer + BM25 scorer, no I/O.
- `cli.ts` - `ocli` entry point. yargs command tree: `profiles add|remove|list`, `use`, `commands`, and dynamic per-spec commands. Builds the `axios` request from a `CliCommand` + parsed args; injects auth, custom headers, server URL overrides.
- `version.ts` - generated by `scripts/generate-version.js` during `prebuild`. Do not edit by hand.

## Design principles

1. **OpenAPI-driven**: commands and their options come from the spec. No hand-maintained registry.
2. **Profiles**: every API connection is named; `profiles.ini` is the source of truth. Global vs local `.ocli/` priority is decided by `ConfigLocator`.
3. **Spec cache**: never re-download a spec on every invocation. Refresh is explicit (`onboard`/profile add or refresh flag).
4. **Pure transform layer**: `bm25.ts`, `openapi-to-commands.ts`, and `command-search.ts` perform no I/O; they take inputs and return outputs. This keeps them trivially unit-testable.
5. **Side effects at the edges**: filesystem in `config.ts`/`profile-store.ts`/`openapi-loader.ts`, network in `cli.ts` via `HttpClient`. Inject these via constructors (`fs`, `httpClient`) so tests can swap them.
6. **TypeScript strict**: `strict: true` in `tsconfig.json`. Explicit types for exported functions and public interfaces.
7. **No surprise breaking changes**: every CLI-visible change must be reflected in `README.md`.

## Layers and allowed dependencies

```
Layer 0 (pure) bm25.ts, version.ts, types in openapi-to-commands.ts
Layer 1 (I/O wrappers) config.ts, profile-store.ts, openapi-loader.ts
Layer 2 (transform) openapi-to-commands.ts (uses Profile), command-search.ts (uses CliCommand + bm25)
Layer 3 (entry) cli.ts (uses everything above; only this layer talks to yargs/axios/process)
```

- **config** - locate and select `.ocli` directory, resolve `profiles.ini` paths with global and local priority.
- **profile-store** - read and write profile INI files (`profiles.ini`), select current profile.
- **openapi-loader** - load spec from URL or file and cache it into `.ocli/specs/<profile>.json`.
- **openapi-to-commands** - parse OpenAPI, apply include/exclude filters, build command names and option schemas.
- **cli** - entry point, argument parser, command registration, help rendering.
Lower layers must not import from higher layers. New behavior should live in the lowest layer where it makes sense - prefer adding to Layer 2 over expanding `cli.ts`.

### Design Principles
## Repository layout

1. **OpenAPI-driven** - the list of commands and their parameters is defined by the spec.
2. **Profiles** - all API settings are configured via profiles (global or local).
3. **Spec cache** - the spec is cached under `.ocli/specs` to avoid fetching it on every run.
4. **TypeScript strict** - `strict: true`; explicit types for public APIs and profile interfaces.
5. **TDD for core logic** - unit tests for profile parsing, spec loading and command mapping.
6. **Language** - all documentation and code comments for this project must be written in English.
- `src/` - production code (see components above).
- `tests/` - Jest test files (`*.test.ts`), fixtures under `tests/fixtures/`, recorded results under `tests/results/`.
- `examples/skill-ocli-api.md` - example Claude Code skill describing the agent workflow.
- `skills/ocli-api/SKILL.md` - portable OpenClaw skill.
- `benchmarks/benchmark.ts` - token-overhead comparison (MCP variants vs CLI).
- `scripts/generate-version.js` - writes `src/version.ts` before build.
- `.ocli/` - working dir at runtime (not part of source). Never committed.
- `dist/` - `tsc` build output.

### Repository Layout (for the openapi-to-cli directory)
## When extending the spec parser

- `README.md` - concept and description of the CLI and profiles.
- `package.json` - npm package with the `ocli` binary.
- `tsconfig.json` - TypeScript config with strict mode.
- `jest.config.js` - Jest configuration.
- `src/`:
- `cli.ts` - `ocli` binary entry point.
- future modules: `config`, `profile-store`, `openapi-loader`, `openapi-to-commands`, etc.
- `tests/` - tests for the modules above.
Real-world OpenAPI/Swagger documents drift from any single example. Before changing `openapi-to-commands.ts` or `openapi-loader.ts`:

- Add a minimal fixture under `tests/fixtures/` that reproduces the case (don't hand-edit `box-api-yaml.test.ts` or `github-api.test.ts` fixtures - those are real specs).
- Cover both OAS 3 (`requestBody`, `components/schemas`) and Swagger 2 (`body`/`formData`, `definitions`) when the change affects request building.
- Mention the new spec feature in the README "Broader spec support" or "Better request generation" section.
58 changes: 39 additions & 19 deletions .cursor/rules/code-style.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,52 @@ globs: "**/*.ts"
alwaysApply: true
---

## Code Style Rules
# Code style (TypeScript)

### General rules
Applies to every `.ts` file in `src/` and `tests/`.

1. All code comments for this project must be written in English.
2. All documentation files for this project must be written in English.
3. New files must end with a single empty line.
4. Use straight double quotes `"`. Use the regular dash `-` for punctuation, do not use long dashes.
## General

### TypeScript
1. All code comments and identifiers in English. Documentation files in English.
2. New files end with a single trailing newline.
3. Straight double quotes `"` for string literals. Regular hyphen `-`, not em-dashes, in any English prose inside code or docs.
4. Default to no comments. Add a comment only when the **why** is non-obvious (workaround, hidden constraint, subtle invariant). Identifiers should carry intent.

- `strict: true` is enabled in `tsconfig.json`.
- For exported functions and public APIs, prefer explicit parameter and return types.
- For reusable object shapes, use `interface`.
## TypeScript

### File structure
- `strict: true` in `tsconfig.json`. Do not weaken it locally.
- Exported functions and public APIs declare explicit parameter and return types. Local variables may use inference.
- Use `interface` for reusable object shapes, `type` for unions, intersections, and mapped types.
- Avoid `any`. When unavoidable, scope it as narrowly as possible and annotate `// eslint-disable-next-line @typescript-eslint/no-explicit-any` with a one-line reason (see `cli.ts:HttpClient` for the canonical example).
- Prefer `unknown` over `any` at module boundaries; narrow with type guards.

1. Imports first (Node/third-party, then local).
2. Then constants and types.
3. Then main logic (functions, classes, exports).
## File structure

### Naming
1. Imports first - Node built-ins (`path`, `fs`), then third-party (`axios`, `yargs`, `js-yaml`, `zod`, `ini`), then local relative imports.
2. Types and interfaces.
3. Module-level constants.
4. Functions and classes.
5. `export` last when it improves readability; `export class` / `export function` inline is also fine.

- Classes - PascalCase.
- Functions and methods - camelCase.
- Configuration constants - UPPER_SNAKE_CASE or meaningful camelCase names.
- Files - kebab-case or camelCase, consistent with existing files.
## Naming

- Classes - `PascalCase` (`ProfileStore`, `OpenapiLoader`, `CommandSearch`).
- Functions and methods - `camelCase` (`loadSpec`, `buildCommands`, `selectProfile`).
- Interfaces and type aliases - `PascalCase` (`Profile`, `CliCommand`, `HttpClient`).
- Constants - `UPPER_SNAKE_CASE` for environment-style globals, otherwise meaningful `camelCase`.
- Files - `kebab-case.ts` matching the dominant exported type (`profile-store.ts` exports `ProfileStore`).

## Error handling

- Throw `Error` subclasses with informative messages; never throw strings.
- At the CLI boundary (`cli.ts`), catch and translate to a user-friendly message + non-zero exit code. Inner layers should let exceptions propagate.
- For external input (HTTP responses, parsed YAML/JSON), validate with `zod` schemas before consuming.

## Async

- Prefer `async/await`. Avoid `.then()` chains in new code.
- Don't fire-and-forget Promises; always `await` or explicitly handle.

## Testing-facing affordances

When a module performs I/O, accept the dependency via a constructor option (`fs`, `httpClient`, `stdout`). This is how `OpenapiLoader`, `ProfileStore`, and the `run()` entry in `cli.ts` are structured; follow the same pattern in new modules.
42 changes: 42 additions & 0 deletions .cursor/rules/implementation-order.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
description: Implementation order for openapi-to-cli (one unit at a time)
globs: "src/**/*.ts"
alwaysApply: false
---

# Implementation order (one unit at a time)

Applies when adding or extending modules under `src/`.

## Layer order (lower first)

The architecture in @architecture.mdc defines four layers. New behavior should be added at the lowest layer it can live in, then composed upward:

1. **Layer 0 - pure** (`bm25.ts`, type-only files): no I/O, no Node built-ins beyond `Buffer`/`URL`/etc.
2. **Layer 1 - I/O wrappers** (`config.ts`, `profile-store.ts`, `openapi-loader.ts`): touch `fs`, network, or env.
3. **Layer 2 - transform** (`openapi-to-commands.ts`, `command-search.ts`): combine Layer 0 + Layer 1 outputs into the CLI command model.
4. **Layer 3 - entry** (`cli.ts`): wire everything together for yargs and axios.

Forbidden: Layer N importing from Layer M when M > N. If a Layer 1 module suddenly needs a Layer 2 type, that is a sign the type belongs lower.

## Steps for a new module or class

1. Decide the layer using @architecture.mdc.
2. Write a failing test in `tests/<module>.test.ts` that describes the smallest useful behavior (see @testing.mdc and @workflow.mdc).
3. Add the minimum implementation in `src/<module>.ts`. Follow @code-style.mdc for types, naming, and constructor-injected I/O.
4. Make the test pass.
5. Run `npm test` to confirm no regressions, then `npm run build` to confirm `tsc` is clean.
6. Only **then** integrate the new module into the layer above (typically `cli.ts`), guarded by its own test.

## Forbidden

- Implementing two unrelated modules in one step before either has tests.
- Wiring a new module into `cli.ts` before its own tests pass.
- Adding optional fields to `Profile`, `CliCommand`, or `CliCommandOption` without a test that exercises the new field.
- Editing real-spec fixtures (`github-api.*`, `box-api-yaml.*`) to "make tests pass" - those represent contracts.

## Allowed

- Stub or fake dependencies (mock `HttpClient`, in-memory `fs`) while a lower layer is incomplete, provided the stub matches the documented contract.
- Refactor a passing module to a cleaner shape after the test suite stays green.
- Extending an existing Layer 2 module with a new transformation, as long as it is covered by a new test and does not import upward.
51 changes: 38 additions & 13 deletions .cursor/rules/testing.mdc
Original file line number Diff line number Diff line change
@@ -1,27 +1,52 @@
---
description: Test rules for openapi-to-cli
globs: "**/tests/**/*.ts, **/*.test.ts"
description: Test conventions for openapi-to-cli (Jest)
globs: "tests/**/*.ts"
alwaysApply: true
---

## Test Writing Rules
# Test conventions (Jest)

### General principles
Applies when editing or adding files under `tests/`.

1. Tests live in the `tests/` directory inside `openapi-to-cli`.
2. Each test must be independent; use mocks for HTTP (axios) and the filesystem when needed.
3. `describe` and `it` names should clearly describe the behavior under test.
## Layout

### Test structure
- All tests live in `tests/` alongside the matching `src/` module.
- Test files: `tests/<module>.test.ts` (one per `src/<module>.ts`).
- Shared inputs in `tests/fixtures/` (OpenAPI/Swagger documents, sample profiles). Recorded outputs in `tests/results/`.
- `github-api.test.ts` and `box-api-yaml.test.ts` are large real-spec regression tests - do not hand-edit their fixtures.

- Test files: `*.test.ts`.
- Grouping by modules, for example: `describe("profile-store", ...)`, `describe("openapi-loader", ...)` etc.
## Structure

### Running tests
- Group with `describe(<moduleName>, ...)`; one nested `describe` per public method or scenario.
- Test names: `it("does X when Y", ...)` - describe behavior, not implementation.
- Arrange / Act / Assert order inside each `it`. Blank lines between the three sections are encouraged.

From the `openapi-to-cli` directory:
## Isolation

- Each `it` is independent. Use `beforeEach`/`afterEach` for setup and teardown, not module-level mutable state.
- Mock `fs` and `axios` (`HttpClient`) through the constructor options the modules expose. Do **not** monkey-patch the real `fs`/`axios` modules in tests.
- For `.ocli/` fixtures, use `fs.mkdtempSync(os.tmpdir() + "/...")` and clean up in `afterEach`.

## Running

```bash
npm test
npm test # full Jest suite
npx jest tests/<file>.test.ts # one file
npx jest tests/<file>.test.ts -t "X" # one test by name
```

## What to assert

- Public behavior visible at the module boundary - return values, written files, requests issued via the mocked `HttpClient`, captured `stdout`.
- For BM25 / `command-search` - assert ranking order and matched commands, not internal scores.
- For `openapi-to-commands` - assert the resulting `CliCommand[]` structure (names, options, body schema), not intermediate spec normalization.

## What not to assert

- Internal helper signatures, private state, exact log strings - those are implementation details.
- Floating-point BM25 scores beyond ranking order.

## Fixtures

- Add new spec fixtures only when an existing one cannot reproduce the case. Keep them minimal: one path, one operation, only the fields needed for the test.
- For tests covering spec features (OAS 3 `requestBody`, Swagger 2 `formData`, multi-file `$ref`, header/cookie params), name the fixture after the feature, e.g. `tests/fixtures/swagger2-formdata.yaml`.
Loading
Loading