@northmoon-labs/social-cli 0.1.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/LICENSE +17 -0
- package/README.md +478 -0
- package/dist/bin/nmsocial.js +14 -0
- package/dist/cli/args.js +103 -0
- package/dist/cli/config.js +69 -0
- package/dist/cli/help.js +21 -0
- package/dist/cli/output.js +386 -0
- package/dist/cli/router.js +82 -0
- package/dist/commands/accounts.js +19 -0
- package/dist/commands/auth.js +4 -0
- package/dist/commands/auto-replies.js +88 -0
- package/dist/commands/comments.js +49 -0
- package/dist/commands/materials.js +19 -0
- package/dist/commands/posts.js +40 -0
- package/dist/commands/publish.js +69 -0
- package/dist/commands/session.js +40 -0
- package/dist/commands/stats.js +65 -0
- package/dist/commands/tasks.js +17 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/update.js +138 -0
- package/dist/commands/webhook.js +16 -0
- package/dist/commands/youtube.js +16 -0
- package/dist/social/client.js +95 -0
- package/dist/social/types.js +47 -0
- package/dist/utils/query.js +30 -0
- package/dist/utils/stdin.js +17 -0
- package/dist/utils/validation.js +76 -0
- package/package.json +50 -0
- package/skills/nmsocial-cli/SKILL.md +155 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Northmoon Labs
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# Northmoon Social CLI
|
|
2
|
+
|
|
3
|
+
`nmsocial` 是给 Agent 使用的 Northmoon Social 命令行工具,让 Agent 可以通过稳定的命令完成社媒账号授权、素材管理、内容发布、评论处理、数据统计和自动回复配置。
|
|
4
|
+
|
|
5
|
+
你可以使用 `nmsocial` 完成:
|
|
6
|
+
|
|
7
|
+
- 获取社媒账号授权链接
|
|
8
|
+
- 查询已授权账号
|
|
9
|
+
- 上传视频和图片素材
|
|
10
|
+
- 创建视频 / 图文发布任务
|
|
11
|
+
- 查询发布任务和发布记录
|
|
12
|
+
- 查询评论并回复评论
|
|
13
|
+
- 配置 webhook 转发地址
|
|
14
|
+
- 查询账号、作品、平台统计数据
|
|
15
|
+
- 管理自动回复策略
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @northmoon-labs/social-cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
检查是否安装成功:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
nmsocial --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
也可以直接用 `npx` 运行:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx @northmoon-labs/social-cli --help
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 登录
|
|
36
|
+
|
|
37
|
+
推荐使用 `--api-key-stdin` 登录,避免 API Key 出现在 shell history 中:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
nmsocial login --api-key-stdin
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
如果是 Agent 或自动化客户端调用,也可以直接传入 API Key:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
nmsocial login --api-key YOUR_KEY
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
使用 `--api-key` 更方便自动化,但要注意不要把包含 API Key 的命令输出到公开日志中。
|
|
50
|
+
|
|
51
|
+
查看当前登录状态:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
nmsocial status
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
退出登录:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
nmsocial logout
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 更新 CLI
|
|
64
|
+
|
|
65
|
+
检查是否有新版本:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
nmsocial update check
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
更新到 npm 上的最新版本:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
nmsocial update
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
只查看将要执行的安装命令,不实际更新:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
nmsocial update --dry-run
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
默认会根据当前运行环境选择包管理器,无法判断时使用 `npm`。你也可以手动指定:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
nmsocial update --manager npm
|
|
87
|
+
nmsocial update --manager pnpm
|
|
88
|
+
nmsocial update --manager bun
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 给 Agent 使用
|
|
92
|
+
|
|
93
|
+
npm 包内包含一份给 Agent 阅读的 CLI 使用说明:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
skills/nmsocial-cli/SKILL.md
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
如果你的 Agent 支持读取 skill、工具说明或外部上下文,可以把这个文件提供给它。该文件只说明如何调用 `nmsocial`,不包含内部开发文档。
|
|
100
|
+
|
|
101
|
+
## 全局参数
|
|
102
|
+
|
|
103
|
+
| 参数 | 说明 |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `--json` | 输出稳定 JSON,适合脚本解析 |
|
|
106
|
+
| `--timeout MS` | 设置请求超时时间,单位毫秒 |
|
|
107
|
+
| `--dry-run` | 只预览请求内容,不发送请求 |
|
|
108
|
+
| `--help` | 查看帮助 |
|
|
109
|
+
| `--version` | 查看版本 |
|
|
110
|
+
|
|
111
|
+
## 输出格式
|
|
112
|
+
|
|
113
|
+
默认输出为简洁文本或表格,适合在终端里快速查看,也能减少 Agent 阅读结果时的无关内容。
|
|
114
|
+
|
|
115
|
+
如果要让脚本或 Agent 稳定解析返回结果,请加上 `--json`:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
nmsocial accounts list --platform tiktok --json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 平台参数
|
|
122
|
+
|
|
123
|
+
涉及平台的命令支持平台名称或平台数字:
|
|
124
|
+
|
|
125
|
+
| 平台名称 | 平台数字 |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| `tiktok` | `1` |
|
|
128
|
+
| `youtube` | `2` |
|
|
129
|
+
| `instagram` | `3` |
|
|
130
|
+
| `facebook` | `4` |
|
|
131
|
+
|
|
132
|
+
示例:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
nmsocial auth-url --platform tiktok
|
|
136
|
+
nmsocial auth-url --platform 1
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 账号授权
|
|
140
|
+
|
|
141
|
+
获取授权链接:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
nmsocial auth-url --platform tiktok
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
查询已授权账号:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
nmsocial accounts list --platform tiktok --status 1 --page 1 --page-size 20
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
按条件筛选账号:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
nmsocial accounts list \
|
|
157
|
+
--platform youtube \
|
|
158
|
+
--username demo \
|
|
159
|
+
--is-verified 1 \
|
|
160
|
+
--page-size 50
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## YouTube 剧集
|
|
164
|
+
|
|
165
|
+
查询剧集列表:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
nmsocial youtube series list --open-id CHANNEL_ID
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
创建剧集:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
nmsocial youtube series create \
|
|
175
|
+
--open-id CHANNEL_ID \
|
|
176
|
+
--title "Demo Series" \
|
|
177
|
+
--description "Demo description"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
创建前预览请求内容:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
nmsocial youtube series create \
|
|
184
|
+
--open-id CHANNEL_ID \
|
|
185
|
+
--title "Demo Series" \
|
|
186
|
+
--dry-run \
|
|
187
|
+
--json
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## 素材
|
|
191
|
+
|
|
192
|
+
查询素材列表:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
nmsocial materials list --type 1 --title demo --page 1 --page-size 20
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
上传素材:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
nmsocial materials upload ./demo.mp4
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
支持的文件类型:
|
|
205
|
+
|
|
206
|
+
- 视频:`mp4`、`mov`、`avi`、`mkv`、`webm`、`flv`、`wmv`、`m4v`、`3gp`
|
|
207
|
+
- 图片:`jpg`、`jpeg`、`png`、`gif`、`bmp`、`webp`、`heic`、`heif`、`tif`、`tiff`
|
|
208
|
+
|
|
209
|
+
上传后的素材默认保留七天。
|
|
210
|
+
|
|
211
|
+
## 发布任务
|
|
212
|
+
|
|
213
|
+
创建视频发布任务:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
nmsocial publish video \
|
|
217
|
+
--account OPEN_ID_1 \
|
|
218
|
+
--account OPEN_ID_2 \
|
|
219
|
+
--content https://example.com/video1.mp4 \
|
|
220
|
+
--caption "hello"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
创建图文发布任务:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
nmsocial publish image \
|
|
227
|
+
--account OPEN_ID \
|
|
228
|
+
--content https://example.com/image1.png \
|
|
229
|
+
--content https://example.com/image2.png \
|
|
230
|
+
--title "title" \
|
|
231
|
+
--caption "caption"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
定时发布:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
nmsocial publish video \
|
|
238
|
+
--account OPEN_ID \
|
|
239
|
+
--content https://example.com/video.mp4 \
|
|
240
|
+
--at 2026-07-18T10:00:00+08:00
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
创建前预览请求内容:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
nmsocial publish video \
|
|
247
|
+
--account OPEN_ID \
|
|
248
|
+
--content https://example.com/video.mp4 \
|
|
249
|
+
--video-assign-type 1 \
|
|
250
|
+
--privacy public \
|
|
251
|
+
--dry-run \
|
|
252
|
+
--json
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`--account`、`--content`、`--playlist-id` 等参数可以重复传入。
|
|
256
|
+
|
|
257
|
+
## 任务查询
|
|
258
|
+
|
|
259
|
+
查询发布任务:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
nmsocial tasks list --platform tiktok --page 1 --page-size 20
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
按发布方式或时间筛选:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
nmsocial tasks list \
|
|
269
|
+
--platform tiktok \
|
|
270
|
+
--video-assign-type 1 \
|
|
271
|
+
--create-time-begin 1783990000000 \
|
|
272
|
+
--create-time-end 1784000000000
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## 发布记录
|
|
276
|
+
|
|
277
|
+
查询发布记录:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
nmsocial posts list --platform tiktok --open-id OPEN_ID
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
查询已发布作品统计:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
nmsocial posts stats --platform tiktok --main-task-id TASK_ID
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## 评论
|
|
290
|
+
|
|
291
|
+
查询评论:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
nmsocial comments list --platform tiktok --open-id OPEN_ID
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
回复评论:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
nmsocial comments reply \
|
|
301
|
+
--platform tiktok \
|
|
302
|
+
--open-id OPEN_ID \
|
|
303
|
+
--video-id VIDEO_ID \
|
|
304
|
+
--comment-id COMMENT_ID \
|
|
305
|
+
--text "Thanks"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
回复前预览请求内容:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
nmsocial comments reply \
|
|
312
|
+
--platform tiktok \
|
|
313
|
+
--open-id OPEN_ID \
|
|
314
|
+
--video-id VIDEO_ID \
|
|
315
|
+
--comment-id COMMENT_ID \
|
|
316
|
+
--text "Thanks" \
|
|
317
|
+
--dry-run \
|
|
318
|
+
--json
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Webhook
|
|
322
|
+
|
|
323
|
+
设置 webhook 转发地址:
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
nmsocial webhook set --url https://partner.example.com/spost/webhook
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
关闭 webhook 转发:
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
nmsocial webhook disable
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## 数据统计
|
|
336
|
+
|
|
337
|
+
查询统计概览:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
nmsocial stats overview --platform tiktok --start-date 2026-07-11 --end-date 2026-07-17
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
查询账号趋势:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
nmsocial stats account trend --platform tiktok --metric followers
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
查询账号排行:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
nmsocial stats account rank --metric followerIncrease --order desc --page 1 --page-size 20
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
查询作品趋势:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
nmsocial stats post trend --platform tiktok --metric viewCount
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
查询作品排行:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
nmsocial stats post rank --metric viewIncrease --order desc --page 1 --page-size 20
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
查询单个作品趋势:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
nmsocial stats post detail-trend \
|
|
371
|
+
--post-id POST_ID \
|
|
372
|
+
--metric viewCount \
|
|
373
|
+
--metric likeCount \
|
|
374
|
+
--metric commentCount
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
查询平台汇总:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
nmsocial stats platform summary --start-date 2026-07-11 --end-date 2026-07-17
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
日期参数格式为 `yyyy-MM-dd`。
|
|
384
|
+
|
|
385
|
+
## 自动回复
|
|
386
|
+
|
|
387
|
+
查询自动回复策略:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
nmsocial auto-replies list --platform tiktok --page 1 --page-size 20
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
查看策略详情:
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
nmsocial auto-replies detail --id STRATEGY_ID
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
创建策略:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
nmsocial auto-replies create \
|
|
403
|
+
--name "Demo auto reply" \
|
|
404
|
+
--platform tiktok \
|
|
405
|
+
--account-id ACCOUNT_RECORD_ID \
|
|
406
|
+
--trigger-rule keyword \
|
|
407
|
+
--keyword price \
|
|
408
|
+
--keyword coupon \
|
|
409
|
+
--send-rule sequence \
|
|
410
|
+
--content "Thanks for your comment" \
|
|
411
|
+
--content-delay 0 \
|
|
412
|
+
--once-per-user false \
|
|
413
|
+
--repeat-interval-hours 24 \
|
|
414
|
+
--stop-on-user-reply true \
|
|
415
|
+
--enabled true
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
通过 JSON 文件创建:
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
nmsocial auto-replies create --from-file ./auto-reply.json --dry-run --json
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
通过 JSON 文件更新:
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
nmsocial auto-replies update --id STRATEGY_ID --from-file ./auto-reply.json
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
启用、停用或删除策略:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
nmsocial auto-replies enable --id STRATEGY_ID
|
|
434
|
+
nmsocial auto-replies disable --id STRATEGY_ID
|
|
435
|
+
nmsocial auto-replies delete --id STRATEGY_ID
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## JSON 输出
|
|
439
|
+
|
|
440
|
+
需要在脚本中解析输出时,建议使用 `--json`。
|
|
441
|
+
|
|
442
|
+
成功响应:
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
{
|
|
446
|
+
"ok": true,
|
|
447
|
+
"data": {},
|
|
448
|
+
"error": null,
|
|
449
|
+
"meta": {
|
|
450
|
+
"providerCode": 200,
|
|
451
|
+
"providerMessage": "ok",
|
|
452
|
+
"providerTimestamp": "2026-07-14T15:34:08.576+08:00",
|
|
453
|
+
"requestId": "11"
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
错误响应:
|
|
459
|
+
|
|
460
|
+
```json
|
|
461
|
+
{
|
|
462
|
+
"ok": false,
|
|
463
|
+
"data": null,
|
|
464
|
+
"error": {
|
|
465
|
+
"code": "SOCIAL_API_ERROR",
|
|
466
|
+
"message": "invalid api key",
|
|
467
|
+
"status": 403,
|
|
468
|
+
"payload": {}
|
|
469
|
+
},
|
|
470
|
+
"meta": null
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## 使用建议
|
|
475
|
+
|
|
476
|
+
- 创建或更新发布任务、评论回复、webhook、自动回复策略前,可以先使用 `--dry-run --json` 预览请求内容。
|
|
477
|
+
- `nmsocial status` 会隐藏完整 API Key。
|
|
478
|
+
- 不要把 API Key 直接写在 shell 命令中。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgv } from '../cli/args.js';
|
|
3
|
+
import { printError, printSuccess } from '../cli/output.js';
|
|
4
|
+
import { runCommand } from '../cli/router.js';
|
|
5
|
+
const parsed = parseArgv(process.argv.slice(2));
|
|
6
|
+
const json = Boolean(parsed.options.json);
|
|
7
|
+
try {
|
|
8
|
+
const result = await runCommand(parsed);
|
|
9
|
+
printSuccess(result.data, result.provider, json);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
printError(error, json);
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
}
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { CliError } from './output.js';
|
|
2
|
+
const BOOLEAN_FLAGS = new Set([
|
|
3
|
+
'json',
|
|
4
|
+
'verbose',
|
|
5
|
+
'help',
|
|
6
|
+
'version',
|
|
7
|
+
'dry-run',
|
|
8
|
+
'api-key-stdin'
|
|
9
|
+
]);
|
|
10
|
+
export function parseArgv(argv) {
|
|
11
|
+
const positionals = [];
|
|
12
|
+
const options = {};
|
|
13
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
14
|
+
const token = argv[i];
|
|
15
|
+
if (token === '--') {
|
|
16
|
+
positionals.push(...argv.slice(i + 1));
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
if (!token.startsWith('--')) {
|
|
20
|
+
positionals.push(token);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const raw = token.slice(2);
|
|
24
|
+
const eq = raw.indexOf('=');
|
|
25
|
+
const key = eq >= 0 ? raw.slice(0, eq) : raw;
|
|
26
|
+
let parsedValue;
|
|
27
|
+
if (eq >= 0) {
|
|
28
|
+
parsedValue = raw.slice(eq + 1);
|
|
29
|
+
}
|
|
30
|
+
else if (BOOLEAN_FLAGS.has(key)) {
|
|
31
|
+
parsedValue = true;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const next = argv[i + 1];
|
|
35
|
+
if (next === undefined || next.startsWith('--')) {
|
|
36
|
+
throw new CliError('VALIDATION_REQUIRED_OPTION', `--${key} requires a value`);
|
|
37
|
+
}
|
|
38
|
+
parsedValue = next;
|
|
39
|
+
i += 1;
|
|
40
|
+
}
|
|
41
|
+
if (options[key] === undefined) {
|
|
42
|
+
options[key] = parsedValue;
|
|
43
|
+
}
|
|
44
|
+
else if (Array.isArray(options[key])) {
|
|
45
|
+
options[key].push(parsedValue);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
options[key] = [options[key], parsedValue];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { positionals, options };
|
|
52
|
+
}
|
|
53
|
+
export function values(options, key) {
|
|
54
|
+
const value = options[key];
|
|
55
|
+
if (value === undefined)
|
|
56
|
+
return [];
|
|
57
|
+
return (Array.isArray(value) ? value : [value]).map((item) => String(item));
|
|
58
|
+
}
|
|
59
|
+
export function value(options, key) {
|
|
60
|
+
const raw = options[key];
|
|
61
|
+
if (raw === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
const list = Array.isArray(raw) ? raw : [raw];
|
|
64
|
+
const latest = list[list.length - 1];
|
|
65
|
+
if (latest === true)
|
|
66
|
+
return 'true';
|
|
67
|
+
if (latest === false)
|
|
68
|
+
return 'false';
|
|
69
|
+
return latest;
|
|
70
|
+
}
|
|
71
|
+
export function has(options, key) {
|
|
72
|
+
return options[key] !== undefined;
|
|
73
|
+
}
|
|
74
|
+
export function requireOption(options, key) {
|
|
75
|
+
const result = value(options, key);
|
|
76
|
+
if (result === undefined || result === '') {
|
|
77
|
+
throw new CliError('VALIDATION_REQUIRED_OPTION', `missing required option --${key}`);
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
export function intOption(options, key, allowed) {
|
|
82
|
+
const raw = value(options, key);
|
|
83
|
+
if (raw === undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
const parsed = Number(raw);
|
|
86
|
+
if (!Number.isInteger(parsed)) {
|
|
87
|
+
throw new CliError('VALIDATION_INVALID_OPTION', `--${key} must be an integer`);
|
|
88
|
+
}
|
|
89
|
+
if (allowed && !allowed.includes(parsed)) {
|
|
90
|
+
throw new CliError('VALIDATION_INVALID_OPTION', `--${key} must be one of ${allowed.join(', ')}`);
|
|
91
|
+
}
|
|
92
|
+
return parsed;
|
|
93
|
+
}
|
|
94
|
+
export function positiveIntOption(options, key, defaultValue) {
|
|
95
|
+
const raw = value(options, key);
|
|
96
|
+
if (raw === undefined)
|
|
97
|
+
return defaultValue;
|
|
98
|
+
const parsed = Number(raw);
|
|
99
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
100
|
+
throw new CliError('VALIDATION_INVALID_OPTION', `--${key} must be a positive integer`);
|
|
101
|
+
}
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { CliError } from './output.js';
|
|
5
|
+
const CONFIG_PATH = join(homedir(), '.nmsocial', 'config.json');
|
|
6
|
+
const DEFAULT_API_URL = 'https://cloud.spost.ai';
|
|
7
|
+
export function getConfigPath() {
|
|
8
|
+
return CONFIG_PATH;
|
|
9
|
+
}
|
|
10
|
+
export function defaultApiUrl() {
|
|
11
|
+
return DEFAULT_API_URL;
|
|
12
|
+
}
|
|
13
|
+
export async function readLocalConfig() {
|
|
14
|
+
try {
|
|
15
|
+
const text = await readFile(CONFIG_PATH, 'utf8');
|
|
16
|
+
return JSON.parse(text);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (isNodeError(error) && error.code === 'ENOENT')
|
|
20
|
+
return {};
|
|
21
|
+
if (error instanceof SyntaxError) {
|
|
22
|
+
throw new CliError('CONFIG_INVALID_FILE', `invalid config JSON at ${CONFIG_PATH}`);
|
|
23
|
+
}
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function writeLocalConfig(config) {
|
|
28
|
+
await mkdir(dirname(CONFIG_PATH), { recursive: true, mode: 0o700 });
|
|
29
|
+
await writeFile(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
30
|
+
await chmod(dirname(CONFIG_PATH), 0o700).catch(() => { });
|
|
31
|
+
await chmod(CONFIG_PATH, 0o600).catch(() => { });
|
|
32
|
+
}
|
|
33
|
+
export async function deleteLocalConfig() {
|
|
34
|
+
await rm(CONFIG_PATH, { force: true });
|
|
35
|
+
}
|
|
36
|
+
export async function configFileMode() {
|
|
37
|
+
try {
|
|
38
|
+
const info = await stat(CONFIG_PATH);
|
|
39
|
+
return (info.mode & 0o777).toString(8).padStart(3, '0');
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (isNodeError(error) && error.code === 'ENOENT')
|
|
43
|
+
return null;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function resolveConfig(options, { requireApiKey = true } = {}) {
|
|
48
|
+
const local = await readLocalConfig();
|
|
49
|
+
const apiUrl = options['api-url'] || process.env.NMSOCIAL_API_URL || local.apiUrl || DEFAULT_API_URL;
|
|
50
|
+
const apiKey = process.env.NMSOCIAL_API_KEY || local.apiKey;
|
|
51
|
+
validateApiUrl(String(apiUrl));
|
|
52
|
+
if (requireApiKey && !apiKey) {
|
|
53
|
+
throw new CliError('CONFIG_MISSING_API_KEY', 'missing api key; run `nmsocial login --api-key-stdin`');
|
|
54
|
+
}
|
|
55
|
+
return { apiUrl: String(apiUrl), apiKey };
|
|
56
|
+
}
|
|
57
|
+
export function validateApiUrl(apiUrl) {
|
|
58
|
+
try {
|
|
59
|
+
const url = new URL(apiUrl);
|
|
60
|
+
if (!['http:', 'https:'].includes(url.protocol))
|
|
61
|
+
throw new Error('invalid protocol');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new CliError('CONFIG_INVALID_API_URL', `invalid API URL: ${apiUrl}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function isNodeError(error) {
|
|
68
|
+
return error instanceof Error && 'code' in error;
|
|
69
|
+
}
|
package/dist/cli/help.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const HELP = `Northmoon Social CLI
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
nmsocial login --api-key-stdin [--api-url URL]
|
|
5
|
+
nmsocial login --api-key YOUR_KEY
|
|
6
|
+
nmsocial status
|
|
7
|
+
nmsocial update check
|
|
8
|
+
nmsocial update [--manager npm|pnpm|bun]
|
|
9
|
+
nmsocial auth-url --platform tiktok
|
|
10
|
+
nmsocial accounts list [--platform tiktok] [--page 1] [--page-size 20]
|
|
11
|
+
nmsocial materials upload ./demo.mp4
|
|
12
|
+
nmsocial publish video --account OPEN_ID --content URL
|
|
13
|
+
nmsocial webhook set --url https://example.com/webhook
|
|
14
|
+
nmsocial stats overview [--platform tiktok]
|
|
15
|
+
|
|
16
|
+
Global:
|
|
17
|
+
--json
|
|
18
|
+
--api-url URL
|
|
19
|
+
--timeout MS
|
|
20
|
+
--dry-run
|
|
21
|
+
`;
|