Skip to content
Closed
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
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

# One unified gate: lint + typecheck + unit tests + build + dist smoke + e2e.
# The e2e step always runs the mock-server suite; the real-API read paths in
# tests/e2e/cli.e2e.test.ts additionally run when the YUQUE_E2E_TOKEN secret is
# configured (repo Settings → Secrets and variables → Actions). They are enabled
# on the Node 22 leg only, so the live API is hit once per run, and they skip
# gracefully on forks (secrets are absent there). The weekly schedule catches
# upstream API drift even when nothing is pushed.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
schedule:
- cron: '0 2 * * 1'

jobs:
check:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
# npm install, not npm ci: vitest's platform-specific wasm bindings resolve
# different @emnapi versions per OS, and a macOS-generated lockfile trips
# npm ci's strict sync check on Linux (npm/cli#4828 family).
- run: npm install --no-audit --no-fund
- name: npm run check (real-API e2e on the Node 22 leg when the token secret exists)
run: npm run check
env:
YUQUE_E2E: ${{ matrix.node-version == 22 && secrets.YUQUE_E2E_TOKEN != '' && '1' || '' }}
YUQUE_E2E_TOKEN: ${{ secrets.YUQUE_E2E_TOKEN }}
YUQUE_E2E_LOGIN: ${{ vars.YUQUE_E2E_LOGIN }}
YUQUE_E2E_REPO: ${{ vars.YUQUE_E2E_REPO }}
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
*.log

# Build artifacts
dist/
Expand All @@ -21,6 +22,3 @@ coverage/
Thumbs.db
.idea/
.vscode/

# Local runtime state
.yuque-cli/
10 changes: 10 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"endOfLine": "lf"
}
40 changes: 40 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# AGENTS.md

Guidance for AI agents (and humans) working in this repo.

## What this is

`yuque-cli` — a command-line interface for the Yuque (语雀) Open API, sibling of
[yuque-mcp-server](https://github.com/yuque/yuque-mcp-server) and built with the same
engineering conventions.

## Architecture

```
bin.ts → cli.ts (commander program, error → exit code)
└── commands/<domain>.ts (flags, confirmation, rendering)
└── client/api/<domain>.ts (typed calls, envelope unwrap)
└── client/http.ts (auth header, retry/backoff, YuqueError)
```

- Command surface is **locked 1:1 against `spec/yuque-openapi.yaml`** by
`tests/spec-coverage.test.ts`. Adding/renaming a command or refreshing the spec means
updating that table deliberately.
- Both READMEs are locked by `tests/docs/command-surface-docs.test.ts` (exact command
count in the heading, every command mentioned). Keep README.md and README.zh-CN.md
strictly isomorphic.
- Exit codes are a stable contract: 0 ok · 1 API/unknown · 2 usage · 3 auth · 4 not
found · 5 rate limited (`src/errors.ts`).

## Rules

- All HTTP goes through `client/http.ts`; command handlers never call axios directly.
- Destructive commands (`delete`, `member remove`) go through `confirmDestructive` and
support `--yes`.
- Human output via `src/output.ts`; `--json` always prints the full raw payload.
- `npm run check` (lint + format + typecheck + unit tests + build + dist smoke +
functional e2e) must exit 0 — it is the merge gate and matches CI.
- Functional tests live in `tests/e2e/` (see its README): a mock-server suite that
spawns the built binary (always on), plus an env-gated real-API suite that CI
(`.github/workflows/ci.yml`) enables when the `YUQUE_E2E_TOKEN` secret exists.
New/changed commands need e2e coverage in the mock suite, not just unit tests.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Changelog

## 1.0.0

Complete rewrite. Replaces the previous interactive REPL implementation
(`yuque-cli@0.1.x`) with a spec-driven, scriptable CLI:

- 26 noun-verb commands covering all 38 operations of the Yuque OpenAPI —
auth/ping, user, search, repos, docs (incl. version history), TOC, group
members, and team statistics; repos accept a numeric id or `owner/slug`
everywhere.
- Token auth via `YUQUE_TOKEN` / `--token` (flag wins; `YUQUE_PERSONAL_TOKEN`
compatibility fallback); custom hosts via `YUQUE_HOST` / `--host`; timeouts
via `YUQUE_TIMEOUT_MS` / `--timeout`.
- Human-readable output with `--json` full-payload mode, stable exit codes
(0/1/2/3/4/5), `--all` pagination, automatic backoff on rate limits (writes
are never silently replayed), and confirmation gates on destructive commands.
- Command surface locked 1:1 against the vendored OpenAPI spec by contract
tests; functional e2e suite drives the built binary in CI.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 cwg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
177 changes: 123 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,89 +1,158 @@
# Yuque CLI
<div align="center">

An interactive command-line client for [Yuque](https://www.yuque.com), built with Node.js.
<a href="https://www.yuque.com/"><img src="https://avatars.githubusercontent.com/u/34602419?s=200&v=4" width="96" alt="Yuque logo"></a>

<img width="1500" height="936" alt="image" src="https://github.com/user-attachments/assets/a19dc500-c404-4c2a-bd62-9720b7ec72bd" />
<h1>Yuque CLI</h1>

It provides a full-screen REPL with:
- persistent app-style banner
- keyboard-first navigation
- table/list based selection for repos and docs
- `open` and `show` document flows
- markdown pager and in-terminal editor for document body input
Your [Yuque (语雀)](https://www.yuque.com/) knowledge base in the terminal —<br>search, read, write, and manage docs from the command line.

中文文档: [`README.zh-CN.md`](README.zh-CN.md)
[![CI][ci-image]][ci-url] [![npm version][npm-image]][npm-url] [![npm downloads][download-image]][download-url] [![License][license-image]][license-url]

## Features
[Quick Start](#quick-start) · [Commands](#commands-26) · [Scripting](#output--scripting) · [Troubleshooting](#troubleshooting) · [中文文档](./README.zh-CN.md)

- Token auth via `YUQUE_TOKEN` or interactive login
- Repo/doc browsing with rich keyboard interactions
- Clickable repo namespace links (OSC8-capable terminals)
- Tab completion and command suggestions
- Friendly prompts and guidance-focused messaging
- Multi-line body editor for `create doc in [repo]`
</div>

## Requirements
Once authenticated, your knowledge base is one command away:

- Node.js >= 18
```bash
yuque search "canary release" --type doc # find that doc you half-remember
yuque doc get team/handbook onboarding > onboarding.md
yuque doc create team/notes --title "Weekly sync" --body-file weekly.md
yuque repo list my-team --group --all --json | jq '.[].name'
```

## Quick Start

## Install
**1. Get a token** — create one at [Yuque Developer Settings](https://www.yuque.com/settings/tokens). If you use a team token bound to a Yuque space, also note the space host (e.g. `https://your-space.yuque.com`) — you will pass it as `--host` or `YUQUE_HOST`.

**2. Install and sign in:**

```bash
npm install -g yuque-cli
export YUQUE_TOKEN=YOUR_TOKEN
yuque auth status
```

## Verify
<details>
<summary><b>Run without installing (npx)</b></summary>

```bash
yuque --version
YUQUE_TOKEN=YOUR_TOKEN npx yuque-cli auth status
```

## Authentication

- Environment variable: `YUQUE_TOKEN`
- Or run `auth login` in REPL
</details>

**3. Start exploring** — `yuque repo list your-login`, then `yuque doc list <repo>`.

## Configuration

| Setting | Env var / CLI flag | Description |
| -------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Token **(required)** | `YUQUE_TOKEN` / `--token` | Personal or team Yuque API token (also reads `YUQUE_PERSONAL_TOKEN` as a compatibility fallback, e.g. shared with @yuque/mcp-server) |
| Host (optional) | `YUQUE_HOST` / `--host` | Site or space host, e.g. `https://your-space.yuque.com` — required for space-bound team tokens and private deployments |
| Timeout (optional) | `YUQUE_TIMEOUT_MS` / `--timeout` | API request timeout in milliseconds — default `30000` |

Flags win over env vars, so a one-off `--token` override always works. Site roots are normalized (`/api/v2` is appended automatically); when unset, the host defaults to `https://www.yuque.com`.

## Commands (26)

Each command maps to the [Yuque OpenAPI](https://www.yuque.com/yuque/developer/api) — the mapping is locked by a contract test against the vendored spec.

| Category | Command | Description |
| ---------- | ------------------------------------ | ---------------------------------------------------------------------------------- |
| **Auth** | `ping` | Verify connectivity to the Yuque API |
| | `auth status` | Show who you are signed in as |
| **User** | `user info` | Show the authenticated user |
| | `user groups <user>` | List groups a user belongs to |
| **Search** | `search <query>` | Search docs or repos, with paging |
| **Repos** | `repo list <login>` | List repos (知识库) of a user or `--group` |
| | `repo get <repo>` | Show a repo by id or `owner/slug` |
| | `repo create <login>` | Create a repo |
| | `repo update <repo>` | Update name, slug, description, visibility, or TOC |
| | `repo delete <repo>` | Delete a repo — asks for confirmation |
| **Docs** | `doc list <repo>` | List docs in a repo, `--all` drains paging |
| | `doc get <repo> <doc>` | Print a doc's markdown body; also takes a global `<doc-id>`, `--meta` for metadata |
| | `doc create <repo>` | Create a doc from `--body` or `--body-file` |
| | `doc update <repo> <doc>` | Update a doc's body or metadata |
| | `doc delete <repo> <doc>` | Delete a doc — asks for confirmation |
| | `doc versions <doc-id>` | List a doc's version history |
| | `doc version <version-id>` | Show one version's content |
| **TOC** | `toc get <repo>` | Print a repo's table of contents as a tree |
| | `toc update <repo>` | Append, prepend, edit, or remove a TOC node |
| **Groups** | `group members <login>` | List members of a group |
| | `group member set <login> <user>` | Add a member or change their role |
| | `group member remove <login> <user>` | Remove a member — asks for confirmation |
| **Stats** | `stats group <login>` | Group-level statistics |
| | `stats members <login>` | Per-member statistics |
| | `stats books <login>` | Per-repo statistics |
| | `stats docs <login>` | Per-doc statistics |

Repos accept either a numeric id or an `owner/slug` namespace everywhere. Run `yuque <command> --help` for all flags.

## Output & scripting

Human-readable tables and records by default; add `--json` to any command for the full API payload:

Saved token location:

```text
~/.yuque/settings.json
```bash
yuque doc list team/handbook --all --json | jq -r '.[].slug'
```

Token creation page:
Exit codes are stable, so scripts can branch on them:

```text
https://www.yuque.com/settings/tokens
```
| Code | Meaning |
| ---- | -------------------- |
| `0` | Success |
| `1` | API or unknown error |
| `2` | Usage error |
| `3` | Authentication error |
| `4` | Not found |
| `5` | Rate limited |

## Usage
Colors are disabled automatically when piping, or force-off with `NO_COLOR=1`. Rate-limited and transient errors are retried with backoff before failing.

```bash
yuque
```

Or one-shot commands:
## Write access

```bash
yuque whoami
yuque list repos
yuque list docs <repo>
yuque open <repo>/<doc>
yuque show <repo>/<doc>
yuque search "keyword" <repo>
```
`create`, `update`, and `delete` commands modify real content, and the CLI can do whatever your token can do. Destructive commands prompt for confirmation on a TTY and require `--yes` in scripts. Keep the token secret, and prefer a space-scoped team token (with `YUQUE_HOST`) when you only work within one space.

## Environment Variables
## Troubleshooting

- `YUQUE_TOKEN`: auth token
- `YUQUE_ENDPOINT`: override API endpoint (default `https://www.yuque.com`)
| Error | Solution |
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `A Yuque API token is required` | Set `YUQUE_TOKEN=YOUR_TOKEN` or pass `--token=YOUR_TOKEN` |
| `token invalid or expired` (exit `3`) | [Regenerate the token](https://www.yuque.com/settings/tokens) or fix `YUQUE_TOKEN` / `--token` |
| `rate limited by the Yuque API` (exit `5`) | The CLI retries automatically; slow down `--all` loops |
| `the requested resource does not exist` (exit `4`) | Check the repo id / `owner/slug` namespace and the doc slug |
| `npm` command not found | Install [Node.js](https://nodejs.org/) v20 or later |

## Development

```bash
npm test
npm run repl
git clone https://github.com/yuque/yuque-cli.git
cd yuque-cli
npm install
npm test # unit tests
npm run build # compile TypeScript
npm run test:e2e # functional tests: the built binary vs a mock Yuque API
npm run dev -- --help # run from source
```

The command surface is pinned to [spec/yuque-openapi.yaml](./spec/yuque-openapi.yaml) by [tests/spec-coverage.test.ts](./tests/spec-coverage.test.ts); `npm run check` is the merge gate.

## Links

- [Yuque API docs](https://www.yuque.com/yuque/developer/api)
- [yuque-mcp-server](https://github.com/yuque/yuque-mcp-server) — the same knowledge base for AI assistants, via MCP
- [Yuque AI Ecosystem](https://yuque.github.io/yuque-ecosystem/)

## License

MIT
[MIT](./LICENSE)

[ci-image]: https://img.shields.io/github/actions/workflow/status/yuque/yuque-cli/ci.yml?style=flat-square&label=CI
[ci-url]: https://github.com/yuque/yuque-cli/actions/workflows/ci.yml
[npm-image]: https://img.shields.io/npm/v/yuque-cli?style=flat-square
[npm-url]: https://www.npmjs.com/package/yuque-cli
[download-image]: https://img.shields.io/npm/dm/yuque-cli?style=flat-square
[download-url]: https://www.npmjs.com/package/yuque-cli
[license-image]: https://img.shields.io/github/license/yuque/yuque-cli?style=flat-square
[license-url]: ./LICENSE
Loading
Loading