@yizhuan-cli/cli 0.1.3 → 0.1.5-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -11
- package/package.json +4 -2
- package/src/index.js +256 -29
- package/src/params.test.js +54 -0
- package/src/version.test.js +22 -0
package/README.md
CHANGED
|
@@ -34,23 +34,47 @@ yizhuan config path
|
|
|
34
34
|
yizhuan config show
|
|
35
35
|
yizhuan execute --ability membership
|
|
36
36
|
yizhuan execute --ability works --query "百家号昨天新增了哪些作品"
|
|
37
|
-
yizhuan execute --ability
|
|
38
|
-
yizhuan execute --ability
|
|
39
|
-
yizhuan execute --ability
|
|
40
|
-
yizhuan execute --ability radar --params
|
|
37
|
+
yizhuan execute --ability hot_topics --platform douyin
|
|
38
|
+
yizhuan execute --ability hot_topics --params '{"platform":"douyin"}'
|
|
39
|
+
yizhuan execute --ability accounts --params '{"mode":"public_search","platform":"toutiao","keyword":"张三"}'
|
|
40
|
+
yizhuan execute --ability radar --params '{"mode":"following"}'
|
|
41
|
+
yizhuan execute --ability radar --mode timeline --timeRange 7d
|
|
42
|
+
yizhuan execute --ability radar --params '{"mode":"unfollow","articleId":"205169870"}'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### PowerShell 参数说明
|
|
46
|
+
|
|
47
|
+
PowerShell 对 JSON 双引号转义不友好。推荐以下写法:
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
# 最稳妥:独立参数
|
|
51
|
+
yizhuan execute --ability hot_topics --platform douyin
|
|
52
|
+
|
|
53
|
+
# 单引号包 JSON
|
|
54
|
+
yizhuan execute --ability hot_topics --params '{"platform":"douyin"}'
|
|
55
|
+
|
|
56
|
+
# key=value
|
|
57
|
+
yizhuan execute --ability hot_topics --params platform=douyin
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
请避免 bash 风格的:
|
|
61
|
+
|
|
62
|
+
```powershell
|
|
63
|
+
# 在 PowerShell 中容易失败
|
|
64
|
+
yizhuan execute --ability hot_topics --params "{\"platform\":\"douyin\"}"
|
|
41
65
|
```
|
|
42
66
|
|
|
43
67
|
## 支持的典型能力
|
|
44
68
|
|
|
45
69
|
- 作品查询:`yizhuan execute --ability works --query "AI 相关的热门作品有哪些"`
|
|
46
70
|
- 作者查询:`yizhuan execute --ability accounts --query "查询张三这个作者"`
|
|
47
|
-
- 作者全网搜索:`yizhuan execute --ability accounts --params
|
|
48
|
-
- 关注全网作者:`yizhuan execute --ability accounts --params
|
|
49
|
-
- 热点查询:`yizhuan execute --ability hot_topics --
|
|
50
|
-
- 雷达查询:`yizhuan execute --ability radar --params
|
|
51
|
-
- 竞品动态:`yizhuan execute --ability radar --
|
|
52
|
-
- 竞品动态筛选:`yizhuan execute --ability radar --params
|
|
53
|
-
- 取消关注雷达作者:先查询关注列表拿到 `articleId`,再执行 `yizhuan execute --ability radar --params
|
|
71
|
+
- 作者全网搜索:`yizhuan execute --ability accounts --params '{"mode":"public_search","platform":"toutiao","keyword":"张三"}'`
|
|
72
|
+
- 关注全网作者:`yizhuan execute --ability accounts --params '{"mode":"follow_public_author","platform":"toutiao","keyword":"张三","extAuthorId":"author-1"}'`
|
|
73
|
+
- 热点查询:`yizhuan execute --ability hot_topics --platform douyin`
|
|
74
|
+
- 雷达查询:`yizhuan execute --ability radar --params '{"mode":"following"}'`
|
|
75
|
+
- 竞品动态:`yizhuan execute --ability radar --mode timeline --timeRange 7d`
|
|
76
|
+
- 竞品动态筛选:`yizhuan execute --ability radar --params '{"mode":"timeline","timeRange":"7d","platform":"baijiahao","keyword":"AI"}'`
|
|
77
|
+
- 取消关注雷达作者:先查询关注列表拿到 `articleId`,再执行 `yizhuan execute --ability radar --params '{"mode":"unfollow","articleId":"205169870"}'`
|
|
54
78
|
- 会员查询:`yizhuan execute --ability membership --query "我的会员什么时候到期"`
|
|
55
79
|
|
|
56
80
|
如果你是通过 Codex、Cursor、Claude Code 这类 Agent 使用 CLI,也可以直接用自然语言提问,例如:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yizhuan-cli/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5-beta.0",
|
|
4
4
|
"description": "易撰命令行工具,用于通过 API Key 查询易撰真实数据。",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"start": "node src/index.js",
|
|
16
16
|
"pack:dry-run": "npm pack --dry-run",
|
|
17
|
-
"smoke": "node src/index.js --help && node src/
|
|
17
|
+
"smoke": "node src/index.js --help && node --test src/version.test.js",
|
|
18
|
+
"test:params": "node --test src/params.test.js",
|
|
19
|
+
"test:version": "node --test src/version.test.js"
|
|
18
20
|
},
|
|
19
21
|
"engines": {
|
|
20
22
|
"node": ">=22"
|
package/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
6
6
|
|
|
7
7
|
const CLI_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
8
8
|
const PACKAGE_PATH = path.join(CLI_DIR, 'package.json')
|
|
9
|
-
const CONFIG_PATH = path.join(os.homedir(), '.config', 'yizhuan', 'config.json')
|
|
9
|
+
const CONFIG_PATH = process.env.YIZHUAN_CONFIG_PATH || path.join(os.homedir(), '.config', 'yizhuan', 'config.json')
|
|
10
10
|
const ABILITIES = new Set([
|
|
11
11
|
'works',
|
|
12
12
|
'accounts',
|
|
@@ -25,22 +25,49 @@ const ABILITIES = new Set([
|
|
|
25
25
|
'favorites_query'
|
|
26
26
|
])
|
|
27
27
|
|
|
28
|
+
const PARAM_FLAG_KEYS = [
|
|
29
|
+
'platform',
|
|
30
|
+
'mode',
|
|
31
|
+
'keyword',
|
|
32
|
+
'page',
|
|
33
|
+
'pageSize',
|
|
34
|
+
'timeRange',
|
|
35
|
+
'date',
|
|
36
|
+
'startDate',
|
|
37
|
+
'endDate',
|
|
38
|
+
'articleId',
|
|
39
|
+
'authorId',
|
|
40
|
+
'extAuthorId',
|
|
41
|
+
'collectionType',
|
|
42
|
+
'metric'
|
|
43
|
+
]
|
|
44
|
+
|
|
28
45
|
function printHelp() {
|
|
29
46
|
console.log(`易撰 CLI
|
|
30
47
|
|
|
31
48
|
Usage:
|
|
32
49
|
yizhuan --help
|
|
33
50
|
yizhuan --version
|
|
34
|
-
yizhuan execute --ability <ability> [--query <text>] [--params <json>]
|
|
51
|
+
yizhuan execute --ability <ability> [--query <text>] [--params <json>] [--platform <name>]
|
|
52
|
+
yizhuan query --json '<request JSON>' [--format json|table|markdown]
|
|
53
|
+
yizhuan query --json-file <path> [--format json|table|markdown]
|
|
35
54
|
yizhuan config path
|
|
36
55
|
yizhuan config show
|
|
37
56
|
|
|
38
57
|
Examples:
|
|
39
58
|
yizhuan execute --ability works --query "百家号昨天新增了哪些作品"
|
|
40
|
-
yizhuan execute --ability
|
|
41
|
-
yizhuan execute --ability
|
|
42
|
-
yizhuan execute --ability
|
|
43
|
-
yizhuan execute --ability radar --params
|
|
59
|
+
yizhuan execute --ability hot_topics --platform douyin
|
|
60
|
+
yizhuan execute --ability hot_topics --params '{"platform":"douyin"}'
|
|
61
|
+
yizhuan execute --ability accounts --params '{"mode":"public_search","platform":"toutiao","keyword":"张三"}'
|
|
62
|
+
yizhuan execute --ability radar --params '{"mode":"following"}'
|
|
63
|
+
yizhuan execute --ability radar --mode timeline --timeRange 7d
|
|
64
|
+
yizhuan execute --ability radar --params '{"mode":"unfollow","articleId":"205169870"}'
|
|
65
|
+
|
|
66
|
+
Notes:
|
|
67
|
+
- PowerShell 推荐优先使用 --platform / --mode 等独立参数,或单引号 JSON:--params '{"platform":"douyin"}'
|
|
68
|
+
- 所有 Shell 都可用 --json-file request.json,避免 JSON 引号被终端改写
|
|
69
|
+
- bash/zsh 也可用:--params '{"platform":"douyin"}' 或 --platform douyin
|
|
70
|
+
- 兼容 key=value:--params platform=douyin
|
|
44
71
|
|
|
45
72
|
Abilities:
|
|
46
73
|
works 作品查询
|
|
@@ -53,6 +80,7 @@ Abilities:
|
|
|
53
80
|
|
|
54
81
|
Config:
|
|
55
82
|
${CONFIG_PATH}
|
|
83
|
+
可通过环境变量 YIZHUAN_CONFIG_PATH 指定独立配置文件
|
|
56
84
|
`)
|
|
57
85
|
}
|
|
58
86
|
|
|
@@ -65,22 +93,84 @@ function readPackageVersion() {
|
|
|
65
93
|
}
|
|
66
94
|
}
|
|
67
95
|
|
|
68
|
-
function
|
|
96
|
+
function isBalancedJsonLike(text) {
|
|
97
|
+
let brace = 0
|
|
98
|
+
let bracket = 0
|
|
99
|
+
let quote = null
|
|
100
|
+
let escaped = false
|
|
101
|
+
|
|
102
|
+
for (const char of text) {
|
|
103
|
+
if (escaped) {
|
|
104
|
+
escaped = false
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (char === '\\') {
|
|
108
|
+
escaped = true
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
if (quote) {
|
|
112
|
+
if (char === quote) quote = null
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
if (char === '"' || char === "'") {
|
|
116
|
+
quote = char
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if (char === '{') brace += 1
|
|
120
|
+
if (char === '}') brace -= 1
|
|
121
|
+
if (char === '[') bracket += 1
|
|
122
|
+
if (char === ']') bracket -= 1
|
|
123
|
+
if (brace < 0 || bracket < 0) return false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return brace === 0 && bracket === 0 && quote === null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readFlagValue(argv, startIndex, key) {
|
|
130
|
+
let index = startIndex
|
|
131
|
+
let raw = argv[index]
|
|
132
|
+
if (raw == null) return { value: true, nextIndex: startIndex - 1 }
|
|
133
|
+
|
|
134
|
+
if (key === 'params' && /[{\[]/.test(raw)) {
|
|
135
|
+
let joined = raw
|
|
136
|
+
while (!isBalancedJsonLike(joined) && index + 1 < argv.length && !String(argv[index + 1]).startsWith('--')) {
|
|
137
|
+
index += 1
|
|
138
|
+
joined += argv[index]
|
|
139
|
+
}
|
|
140
|
+
return { value: joined, nextIndex: index }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { value: raw, nextIndex: index }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function parseArgs(argv) {
|
|
69
147
|
const args = { _: [] }
|
|
70
148
|
for (let index = 0; index < argv.length; index += 1) {
|
|
71
149
|
const value = argv[index]
|
|
72
|
-
if (value.startsWith('--')) {
|
|
73
|
-
const key = value.slice(2)
|
|
74
|
-
const next = argv[index + 1]
|
|
75
|
-
if (!next || next.startsWith('--')) {
|
|
76
|
-
args[key] = true
|
|
77
|
-
} else {
|
|
78
|
-
args[key] = next
|
|
79
|
-
index += 1
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
150
|
+
if (!value.startsWith('--')) {
|
|
82
151
|
args._.push(value)
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const body = value.slice(2)
|
|
156
|
+
const eqIndex = body.indexOf('=')
|
|
157
|
+
if (eqIndex > 0) {
|
|
158
|
+
const key = body.slice(0, eqIndex)
|
|
159
|
+
const raw = body.slice(eqIndex + 1)
|
|
160
|
+
args[key] = raw
|
|
161
|
+
continue
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const key = body
|
|
165
|
+
const next = argv[index + 1]
|
|
166
|
+
if (!next || next.startsWith('--')) {
|
|
167
|
+
args[key] = true
|
|
168
|
+
continue
|
|
83
169
|
}
|
|
170
|
+
|
|
171
|
+
const read = readFlagValue(argv, index + 1, key)
|
|
172
|
+
args[key] = read.value
|
|
173
|
+
index = read.nextIndex
|
|
84
174
|
}
|
|
85
175
|
return args
|
|
86
176
|
}
|
|
@@ -101,13 +191,107 @@ function readConfig() {
|
|
|
101
191
|
}
|
|
102
192
|
}
|
|
103
193
|
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
194
|
+
function stripWrappingQuotes(text) {
|
|
195
|
+
const value = String(text).trim()
|
|
196
|
+
if (
|
|
197
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
198
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
199
|
+
) {
|
|
200
|
+
return value.slice(1, -1)
|
|
201
|
+
}
|
|
202
|
+
return value
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function coerceScalar(value) {
|
|
206
|
+
const text = stripWrappingQuotes(value).trim()
|
|
207
|
+
if (text === 'true') return true
|
|
208
|
+
if (text === 'false') return false
|
|
209
|
+
if (text === 'null') return null
|
|
210
|
+
if (text !== '' && /^-?\d+(\.\d+)?$/.test(text)) return Number(text)
|
|
211
|
+
return text
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function sanitizeJsonLike(text) {
|
|
215
|
+
return String(text)
|
|
216
|
+
.replace(/^\uFEFF/, '')
|
|
217
|
+
.replace(/[\u201C\u201D]/g, '"')
|
|
218
|
+
.replace(/[\u2018\u2019]/g, "'")
|
|
219
|
+
.replace(/\\+"/g, '"')
|
|
220
|
+
.replace(/\\+'/g, "'")
|
|
221
|
+
.replace(/\\([{}:[\]:,])/g, '$1')
|
|
222
|
+
.replace(/\\/g, '')
|
|
223
|
+
.replace(/\s+/g, ' ')
|
|
224
|
+
.trim()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function parseLooseObject(text) {
|
|
228
|
+
let body = String(text).trim()
|
|
229
|
+
if (!body) return undefined
|
|
230
|
+
|
|
231
|
+
if ((body.startsWith('{') && body.endsWith('}')) || (body.startsWith('[') && body.endsWith(']'))) {
|
|
232
|
+
body = body.slice(1, -1).trim()
|
|
110
233
|
}
|
|
234
|
+
if (!body) return {}
|
|
235
|
+
|
|
236
|
+
const pairs = body
|
|
237
|
+
.split(',')
|
|
238
|
+
.map((part) => part.trim())
|
|
239
|
+
.filter(Boolean)
|
|
240
|
+
if (!pairs.length) return undefined
|
|
241
|
+
|
|
242
|
+
const result = {}
|
|
243
|
+
for (const pair of pairs) {
|
|
244
|
+
const matched = pair.match(
|
|
245
|
+
/^(?:["']?)([A-Za-z_][\w-]*)(?:["']?)\s*[:=]\s*(.+)$/
|
|
246
|
+
)
|
|
247
|
+
if (!matched) return undefined
|
|
248
|
+
result[matched[1]] = coerceScalar(matched[2])
|
|
249
|
+
}
|
|
250
|
+
return result
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function parseParams(value) {
|
|
254
|
+
if (value == null || value === true || value === false) return undefined
|
|
255
|
+
if (typeof value === 'object' && !Array.isArray(value)) return value
|
|
256
|
+
|
|
257
|
+
const original = String(value).trim()
|
|
258
|
+
if (!original) return undefined
|
|
259
|
+
|
|
260
|
+
const candidates = [original, sanitizeJsonLike(original), original.replace(/\\/g, '')]
|
|
261
|
+
for (const candidate of candidates) {
|
|
262
|
+
try {
|
|
263
|
+
const parsed = JSON.parse(candidate)
|
|
264
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed
|
|
265
|
+
} catch {
|
|
266
|
+
// continue
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for (const candidate of candidates) {
|
|
271
|
+
const loose = parseLooseObject(candidate)
|
|
272
|
+
if (loose) return loose
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
throw new Error(
|
|
276
|
+
'--params 必须是 JSON 或 key=value。PowerShell 推荐:--platform douyin 或 --params \'{"platform":"douyin"}\''
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function assignFlagParams(params, args) {
|
|
281
|
+
for (const key of PARAM_FLAG_KEYS) {
|
|
282
|
+
if (args[key] == null || args[key] === true) continue
|
|
283
|
+
if (params[key] != null) continue
|
|
284
|
+
params[key] = coerceScalar(args[key])
|
|
285
|
+
}
|
|
286
|
+
return params
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function buildExecuteParams(args) {
|
|
290
|
+
const params = {
|
|
291
|
+
...(parseParams(args.params) || {})
|
|
292
|
+
}
|
|
293
|
+
assignFlagParams(params, args)
|
|
294
|
+
return Object.keys(params).length > 0 ? params : undefined
|
|
111
295
|
}
|
|
112
296
|
|
|
113
297
|
async function execute(args) {
|
|
@@ -127,8 +311,8 @@ async function execute(args) {
|
|
|
127
311
|
body: JSON.stringify({
|
|
128
312
|
json: {
|
|
129
313
|
ability,
|
|
130
|
-
query: args.query,
|
|
131
|
-
params:
|
|
314
|
+
query: typeof args.query === 'string' ? args.query : undefined,
|
|
315
|
+
params: buildExecuteParams(args)
|
|
132
316
|
}
|
|
133
317
|
})
|
|
134
318
|
})
|
|
@@ -142,6 +326,33 @@ async function execute(args) {
|
|
|
142
326
|
console.log(JSON.stringify(payload.result?.data?.json ?? payload, null, 2))
|
|
143
327
|
}
|
|
144
328
|
|
|
329
|
+
function renderQuery(payload, format) {
|
|
330
|
+
if (format === 'json' || !format) return JSON.stringify(payload, null, 2)
|
|
331
|
+
if (payload.status === 'error') return `${payload.error.code}: ${payload.error.message}`
|
|
332
|
+
const items = payload.data?.items
|
|
333
|
+
if (!Array.isArray(items)) return JSON.stringify(payload, null, 2)
|
|
334
|
+
if (format === 'markdown') return items.map((item, index) => `| ${index + 1} | ${item.title || item.name || item.nickname || ''} |`).join('\n')
|
|
335
|
+
return items.map((item, index) => `${index + 1}\t${item.title || item.name || item.nickname || ''}`).join('\n')
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function query(args) {
|
|
339
|
+
const jsonText = args.jsonFile
|
|
340
|
+
? fs.readFileSync(path.resolve(String(args.jsonFile)), 'utf8')
|
|
341
|
+
: args.json === '-'
|
|
342
|
+
? fs.readFileSync(0, 'utf8')
|
|
343
|
+
: args.json || args.params
|
|
344
|
+
const request = parseParams(jsonText)
|
|
345
|
+
if (!request) throw new Error('query 必须提供 --json 请求对象')
|
|
346
|
+
const config = readConfig()
|
|
347
|
+
const response = await fetch(`${config.apiBaseUrl}/api/agent.query`, {
|
|
348
|
+
method: 'POST', headers: { authorization: config.apiKey, 'content-type': 'application/json', 'x-device-type': 'pc' },
|
|
349
|
+
body: JSON.stringify({ json: request })
|
|
350
|
+
})
|
|
351
|
+
const text = await response.text()
|
|
352
|
+
if (!response.ok) throw new Error(`请求失败 HTTP ${response.status}:${text}`)
|
|
353
|
+
console.log(renderQuery(JSON.parse(text).result?.data?.json ?? JSON.parse(text), args.format || 'json'))
|
|
354
|
+
}
|
|
355
|
+
|
|
145
356
|
async function main() {
|
|
146
357
|
const args = parseArgs(process.argv.slice(2))
|
|
147
358
|
const command = args._[0]
|
|
@@ -171,11 +382,27 @@ async function main() {
|
|
|
171
382
|
await execute(args)
|
|
172
383
|
return
|
|
173
384
|
}
|
|
385
|
+
if (command === 'query') {
|
|
386
|
+
await query(args)
|
|
387
|
+
return
|
|
388
|
+
}
|
|
174
389
|
|
|
175
390
|
throw new Error(`未知命令:${command}`)
|
|
176
391
|
}
|
|
177
392
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
393
|
+
const isDirectRun = (() => {
|
|
394
|
+
const entry = process.argv[1]
|
|
395
|
+
if (!entry) return false
|
|
396
|
+
try {
|
|
397
|
+
return fs.realpathSync(path.resolve(entry)) === fs.realpathSync(fileURLToPath(import.meta.url))
|
|
398
|
+
} catch {
|
|
399
|
+
return false
|
|
400
|
+
}
|
|
401
|
+
})()
|
|
402
|
+
|
|
403
|
+
if (isDirectRun) {
|
|
404
|
+
main().catch((error) => {
|
|
405
|
+
console.error(error.message)
|
|
406
|
+
process.exit(1)
|
|
407
|
+
})
|
|
408
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { buildExecuteParams, parseArgs, parseParams } from './index.js'
|
|
4
|
+
|
|
5
|
+
test('parseParams accepts standard JSON', () => {
|
|
6
|
+
assert.deepEqual(parseParams('{"platform":"douyin"}'), { platform: 'douyin' })
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('parseParams repairs PowerShell-mangled escaped JSON fragments', () => {
|
|
10
|
+
assert.deepEqual(parseParams('{\\platform\\:\\douyin\\}'), { platform: 'douyin' })
|
|
11
|
+
assert.deepEqual(parseParams('{"platform":"douyin"}'.replaceAll('"', '\\"')), {
|
|
12
|
+
platform: 'douyin'
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('parseParams accepts key=value and loose objects', () => {
|
|
17
|
+
assert.deepEqual(parseParams('platform=douyin'), { platform: 'douyin' })
|
|
18
|
+
assert.deepEqual(parseParams('{platform:douyin,mode:list}'), {
|
|
19
|
+
platform: 'douyin',
|
|
20
|
+
mode: 'list'
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('buildExecuteParams merges top-level flags', () => {
|
|
25
|
+
assert.deepEqual(
|
|
26
|
+
buildExecuteParams({
|
|
27
|
+
params: '{"mode":"timeline"}',
|
|
28
|
+
platform: 'douyin',
|
|
29
|
+
timeRange: '7d'
|
|
30
|
+
}),
|
|
31
|
+
{
|
|
32
|
+
mode: 'timeline',
|
|
33
|
+
platform: 'douyin',
|
|
34
|
+
timeRange: '7d'
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('buildExecuteParams prefers explicit params keys over flags', () => {
|
|
40
|
+
assert.deepEqual(
|
|
41
|
+
buildExecuteParams({
|
|
42
|
+
params: 'platform=weibo',
|
|
43
|
+
platform: 'douyin'
|
|
44
|
+
}),
|
|
45
|
+
{ platform: 'weibo' }
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('parseArgs preserves query JSON supplied after --json', () => {
|
|
50
|
+
assert.deepEqual(
|
|
51
|
+
parseArgs(['query', '--json', '{"intent":"author_search","platform":"douyin"}']),
|
|
52
|
+
{ _: ['query'], json: '{"intent":"author_search","platform":"douyin"}' }
|
|
53
|
+
)
|
|
54
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { execFileSync } from 'node:child_process'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import test from 'node:test'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
|
|
8
|
+
const CLI_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
9
|
+
const ENTRY_PATH = path.join(CLI_DIR, 'src', 'index.js')
|
|
10
|
+
const PACKAGE_PATH = path.join(CLI_DIR, 'package.json')
|
|
11
|
+
const expectedVersion = JSON.parse(fs.readFileSync(PACKAGE_PATH, 'utf8')).version
|
|
12
|
+
|
|
13
|
+
for (const flag of ['--version', '-v']) {
|
|
14
|
+
test(`${flag} prints the package version`, () => {
|
|
15
|
+
const output = execFileSync(process.execPath, [ENTRY_PATH, flag], {
|
|
16
|
+
encoding: 'utf8'
|
|
17
|
+
}).trim()
|
|
18
|
+
|
|
19
|
+
assert.equal(output, expectedVersion)
|
|
20
|
+
assert.notEqual(output, '')
|
|
21
|
+
})
|
|
22
|
+
}
|