feat(core): support YAML format output and optimize manifest builder
This commit is contained in:
1 parent
eb9050fadd
commit
ee6669f45d
7 files changed
+70
-7
No files matched your search
@@ -94,9 +94,11 @@ Then use directly:
|
||||
|
||||
```bash
|
||||
opencli list # See all commands
|
||||
opencli list -f yaml # List commands as YAML
|
||||
opencli hackernews top --limit 5 # Public API, no browser
|
||||
opencli bilibili hot --limit 5 # Browser command
|
||||
opencli zhihu hot -f json # JSON output
|
||||
opencli zhihu hot -f yaml # YAML output
|
||||
```
|
||||
|
||||
### Install from source (for developers)
|
||||
@@ -140,11 +142,14 @@ npm install -g @jackwener/opencli@latest
|
||||
|
||||
## Output Formats
|
||||
|
||||
Commands support various format outputs:
|
||||
All built-in commands support `--format` / `-f` with `table`, `json`, `yaml`, `md`, and `csv`.
|
||||
The `list` command supports the same format options, and keeps `--json` for backward compatibility.
|
||||
|
||||
```bash
|
||||
opencli list -f yaml # Command registry as YAML
|
||||
opencli bilibili hot -f table # Default: rich terminal table
|
||||
opencli bilibili hot -f json # JSON (pipe to jq or LLMs)
|
||||
opencli bilibili hot -f yaml # YAML (human-readable structured output)
|
||||
opencli bilibili hot -f md # Markdown
|
||||
opencli bilibili hot -f csv # CSV
|
||||
opencli bilibili hot -v # Verbose: show pipeline debug steps
|
||||
|
||||
+6
-1
@@ -94,9 +94,11 @@ npm install -g @jackwener/opencli
|
||||
|
||||
```bash
|
||||
opencli list # 查看所有命令
|
||||
opencli list -f yaml # 以 YAML 列出所有命令
|
||||
opencli hackernews top --limit 5 # 公共 API,无需浏览器
|
||||
opencli bilibili hot --limit 5 # 浏览器命令
|
||||
opencli zhihu hot -f json # JSON 输出
|
||||
opencli zhihu hot -f yaml # YAML 输出
|
||||
```
|
||||
|
||||
### 从源码安装(面向开发者)
|
||||
@@ -140,11 +142,14 @@ npm install -g @jackwener/opencli@latest
|
||||
|
||||
## 输出格式
|
||||
|
||||
所有的命令都支持这些结果格式控制参数:
|
||||
所有内置命令都支持 `--format` / `-f`,可选值为 `table`、`json`、`yaml`、`md`、`csv`。
|
||||
`list` 命令也支持同样的格式参数,同时继续兼容 `--json`。
|
||||
|
||||
```bash
|
||||
opencli list -f yaml # 用 YAML 列出命令注册表
|
||||
opencli bilibili hot -f table # 默认:富文本表格
|
||||
opencli bilibili hot -f json # JSON(适合传给 jq 或者各类 AI Agent)
|
||||
opencli bilibili hot -f yaml # YAML(更适合人类直接阅读)
|
||||
opencli bilibili hot -f md # Markdown
|
||||
opencli bilibili hot -f csv # CSV
|
||||
opencli bilibili hot -v # 详细模式:展示管线执行步骤调试信息
|
||||
|
||||
@@ -134,6 +134,7 @@ opencli ctrip search --query "三亚" # 搜索目的地
|
||||
```bash
|
||||
opencli list # List all commands
|
||||
opencli list --json # JSON output
|
||||
opencli list -f yaml # YAML output
|
||||
opencli validate # Validate all CLI definitions
|
||||
opencli validate bilibili # Validate specific site
|
||||
```
|
||||
@@ -162,11 +163,14 @@ opencli verify <site/name> --smoke
|
||||
|
||||
## Output Formats
|
||||
|
||||
All commands support `--format` / `-f`:
|
||||
All built-in commands support `--format` / `-f` with `table`, `json`, `yaml`, `md`, and `csv`.
|
||||
The `list` command supports the same formats and also keeps `--json` as a compatibility alias.
|
||||
|
||||
```bash
|
||||
opencli list -f yaml # YAML command registry
|
||||
opencli bilibili hot -f table # Default: rich table
|
||||
opencli bilibili hot -f json # JSON (pipe to jq, feed to AI agent)
|
||||
opencli bilibili hot -f yaml # YAML (readable structured output)
|
||||
opencli bilibili hot -f md # Markdown
|
||||
opencli bilibili hot -f csv # CSV
|
||||
```
|
||||
|
||||
Generated
+1
-1
@@ -7,7 +7,7 @@
|
||||
"": {
|
||||
"name": "@jackwener/opencli",
|
||||
"version": "0.4.2",
|
||||
"license": "MIT",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"cli-table3": "^0.6.5",
|
||||
|
||||
@@ -117,6 +117,10 @@ function scanTs(filePath: string, site: string): ManifestEntry {
|
||||
const stratMatch = src.match(/strategy\s*:\s*Strategy\.(\w+)/);
|
||||
if (stratMatch) entry.strategy = stratMatch[1].toLowerCase();
|
||||
|
||||
// Extract browser: false (some adapters bypass browser entirely)
|
||||
const browserMatch = src.match(/browser\s*:\s*(true|false)/);
|
||||
if (browserMatch) entry.browser = browserMatch[1] === 'true';
|
||||
|
||||
// Extract columns
|
||||
const colMatch = src.match(/columns\s*:\s*\[([^\]]*)\]/);
|
||||
if (colMatch) {
|
||||
|
||||
+21
-3
@@ -31,11 +31,29 @@ program.name('opencli').description('Make any website your CLI. Zero setup. AI-p
|
||||
|
||||
// ── Built-in commands ──────────────────────────────────────────────────────
|
||||
|
||||
program.command('list').description('List all available CLI commands').option('--json', 'JSON output')
|
||||
program.command('list').description('List all available CLI commands').option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table').option('--json', 'JSON output (deprecated)')
|
||||
.action((opts) => {
|
||||
const registry = getRegistry();
|
||||
const commands = [...registry.values()].sort((a, b) => fullName(a).localeCompare(fullName(b)));
|
||||
if (opts.json) { console.log(JSON.stringify(commands.map(c => ({ command: fullName(c), site: c.site, name: c.name, description: c.description, strategy: strategyLabel(c), browser: c.browser, args: c.args.map(a => a.name) })), null, 2)); return; }
|
||||
const rows = commands.map(c => ({
|
||||
command: fullName(c),
|
||||
site: c.site,
|
||||
name: c.name,
|
||||
description: c.description,
|
||||
strategy: strategyLabel(c),
|
||||
browser: c.browser,
|
||||
args: c.args.map(a => a.name).join(', '),
|
||||
}));
|
||||
const fmt = opts.json && opts.format === 'table' ? 'json' : opts.format;
|
||||
if (fmt !== 'table') {
|
||||
renderOutput(rows, {
|
||||
fmt,
|
||||
columns: ['command', 'site', 'name', 'description', 'strategy', 'browser', 'args'],
|
||||
title: 'opencli/list',
|
||||
source: 'opencli list',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const sites = new Map<string, CliCommand[]>();
|
||||
for (const cmd of commands) { const g = sites.get(cmd.site) ?? []; g.push(cmd); sites.set(cmd.site, g); }
|
||||
console.log(); console.log(chalk.bold(' opencli') + chalk.dim(' — available commands')); console.log();
|
||||
@@ -89,7 +107,7 @@ for (const [, cmd] of registry) {
|
||||
else if (arg.default != null) subCmd.option(flag, arg.help ?? '', String(arg.default));
|
||||
else subCmd.option(flag, arg.help ?? '');
|
||||
}
|
||||
subCmd.option('-f, --format <fmt>', 'Output format: table, json, md, csv', 'table').option('-v, --verbose', 'Debug output', false);
|
||||
subCmd.option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table').option('-v, --verbose', 'Debug output', false);
|
||||
|
||||
subCmd.action(async (actionOpts) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { render } from './output.js';
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('render', () => {
|
||||
it('renders YAML output', () => {
|
||||
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
render([{ title: 'Hello', rank: 1 }], { fmt: 'yaml' });
|
||||
|
||||
expect(log).toHaveBeenCalledOnce();
|
||||
expect(log.mock.calls[0]?.[0]).toContain('- title: Hello');
|
||||
expect(log.mock.calls[0]?.[0]).toContain('rank: 1');
|
||||
});
|
||||
|
||||
it('renders yml alias as YAML output', () => {
|
||||
const log = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
render({ title: 'Hello' }, { fmt: 'yml' });
|
||||
|
||||
expect(log).toHaveBeenCalledOnce();
|
||||
expect(log.mock.calls[0]?.[0]).toContain('title: Hello');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user