@skills-store/rednote 0.1.14 → 0.1.16

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 CHANGED
@@ -36,10 +36,10 @@ For most tasks, run commands in this order:
36
36
  rednote env
37
37
  rednote browser create --name seller-main --browser chrome --port 9222
38
38
  rednote browser connect --instance seller-main
39
- rednote login --instance seller-main
40
- rednote status --instance seller-main
41
- rednote search --instance seller-main --keyword 护肤
42
- rednote interact --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect --comment "写得真好"
39
+ rednote login
40
+ rednote status
41
+ rednote search --keyword 护肤
42
+ rednote interact --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect --comment "写得真好"
43
43
  ```
44
44
 
45
45
  ## Commands
@@ -68,7 +68,7 @@ Use `env` first when checking installation, runtime info, or storage paths.
68
68
  ### `status`
69
69
 
70
70
  ```bash
71
- rednote status --instance seller-main
71
+ rednote status
72
72
  ```
73
73
 
74
74
  Use `status` to confirm whether an instance exists, is running, and appears logged in.
@@ -76,7 +76,7 @@ Use `status` to confirm whether an instance exists, is running, and appears logg
76
76
  ### `check-login`
77
77
 
78
78
  ```bash
79
- rednote check-login --instance seller-main
79
+ rednote check-login
80
80
  ```
81
81
 
82
82
  Use `check-login` when you only want to verify whether the session is still valid.
@@ -84,7 +84,7 @@ Use `check-login` when you only want to verify whether the session is still vali
84
84
  ### `login`
85
85
 
86
86
  ```bash
87
- rednote login --instance seller-main
87
+ rednote login
88
88
  ```
89
89
 
90
90
  Use `login` after `browser connect` if the instance is not authenticated yet.
@@ -92,7 +92,7 @@ Use `login` after `browser connect` if the instance is not authenticated yet.
92
92
  ### `home`
93
93
 
94
94
  ```bash
95
- rednote home --instance seller-main --format md --save
95
+ rednote home --format md --save
96
96
  ```
97
97
 
98
98
  Use `home` when you want the current home feed and optionally want to save it to disk.
@@ -103,6 +103,10 @@ The terminal output always uses the compact summary format below, even when `--f
103
103
  id=<database nanoid>
104
104
  title=<post title>
105
105
  like=<liked count>
106
+
107
+ id=...
108
+ title=...
109
+ like=...
106
110
  ```
107
111
 
108
112
  Captured home feed posts are upserted into `~/.skills-router/rednote/main.db` (the same path returned by `rednote env`). They are stored in the `rednote_posts` table, and the printed `id` is that table's `nanoid(16)` primary key.
@@ -110,8 +114,8 @@ Captured home feed posts are upserted into `~/.skills-router/rednote/main.db` (t
110
114
  ### `search`
111
115
 
112
116
  ```bash
113
- rednote search --instance seller-main --keyword 护肤
114
- rednote search --instance seller-main --keyword 护肤 --format json --save ./output/search.jsonl
117
+ rednote search --keyword 护肤
118
+ rednote search --keyword 护肤 --format json --save ./output/search.jsonl
115
119
  ```
116
120
 
117
121
  Use `search` for keyword-based note lookup.
@@ -129,8 +133,8 @@ Captured search results are also upserted into `~/.skills-router/rednote/main.db
129
133
  ### `get-feed-detail`
130
134
 
131
135
  ```bash
132
- rednote get-feed-detail --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy"
133
- rednote get-feed-detail --instance seller-main --id AynQ7_utnNiW1Ytk
136
+ rednote get-feed-detail --id <nanoid>
137
+ rednote get-feed-detail --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy"
134
138
  ```
135
139
 
136
140
  Use `get-feed-detail` when you already have a Xiaohongshu note URL, or when you have a database `id` returned by `home` or `search`. With `--id`, the CLI looks up the saved URL from `~/.skills-router/rednote/main.db` and then navigates with that raw URL.
@@ -140,7 +144,7 @@ Captured note details and comments are also upserted into `~/.skills-router/redn
140
144
  ### `get-profile`
141
145
 
142
146
  ```bash
143
- rednote get-profile --instance seller-main --id USER_ID
147
+ rednote get-profile --id USER_ID
144
148
  ```
145
149
 
146
150
  Use `get-profile` when you want author or account profile information.
@@ -149,8 +153,8 @@ Use `get-profile` when you want author or account profile information.
149
153
  ### `interact`
150
154
 
151
155
  ```bash
152
- rednote interact --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect
153
- rednote interact --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect --comment "写得真好"
156
+ rednote interact --id <nanoid> --like --collect
157
+ rednote interact --id <nanoid> --like --collect --comment "写得真好"
154
158
  ```
155
159
 
156
160
  Use `interact` when you want the single entrypoint for note operations such as like, collect, and comment in one command. Use `--comment TEXT` for replies; there is no standalone `comment` command.
@@ -168,318 +172,6 @@ Use `interact` when you want the single entrypoint for note operations such as l
168
172
  - `--url` is required for `interact`; at least one of `--like`, `--collect`, or `--comment TEXT` must be provided.
169
173
  - replies are sent with `interact --comment TEXT`.
170
174
 
171
- ## JSON success shapes
172
-
173
- Use these shapes as the success model when a command returns JSON.
174
-
175
- ### Shared note item
176
-
177
- `home`, `search`, and `profile.notes` share the normalized `RednotePost` shape:
178
-
179
- ```json
180
- {
181
- "id": "string",
182
- "modelType": "string",
183
- "xsecToken": "string|null",
184
- "url": "string",
185
- "noteCard": {
186
- "type": "string|null",
187
- "displayTitle": "string|null",
188
- "cover": {
189
- "urlDefault": "string|null",
190
- "urlPre": "string|null",
191
- "url": "string|null",
192
- "fileId": "string|null",
193
- "width": "number|null",
194
- "height": "number|null",
195
- "infoList": [{ "imageScene": "string|null", "url": "string|null" }]
196
- },
197
- "user": {
198
- "userId": "string|null",
199
- "nickname": "string|null",
200
- "nickName": "string|null",
201
- "avatar": "string|null",
202
- "xsecToken": "string|null"
203
- },
204
- "interactInfo": {
205
- "liked": "boolean",
206
- "likedCount": "string|null",
207
- "commentCount": "string|null",
208
- "collectedCount": "string|null",
209
- "sharedCount": "string|null"
210
- },
211
- "cornerTagInfo": [{ "type": "string|null", "text": "string|null" }],
212
- "imageList": [{ "width": "number|null", "height": "number|null", "infoList": [{ "imageScene": "string|null", "url": "string|null" }] }],
213
- "video": { "duration": "number|null" }
214
- }
215
- }
216
- ```
217
-
218
- ### `env --format json`
219
-
220
- `env` is the main exception: it returns a raw environment object instead of `{ "ok": true, ... }`.
221
-
222
- ```json
223
- {
224
- "packageRoot": "string",
225
- "homeDir": "string",
226
- "platform": "string",
227
- "nodeVersion": "string",
228
- "storageHome": "string",
229
- "storageRoot": "string",
230
- "databasePath": "string",
231
- "instancesDir": "string",
232
- "instanceStorePath": "string",
233
- "legacyPackageInstancesDir": "string"
234
- }
235
- ```
236
-
237
- ### Browser commands
238
-
239
- `browser list`:
240
-
241
- ```json
242
- {
243
- "lastConnect": { "scope": "default|custom", "name": "string", "browser": "chrome|edge|chromium|brave" } | null,
244
- "instances": [{
245
- "type": "chrome|edge|chromium|brave",
246
- "name": "string",
247
- "executablePath": "string",
248
- "userDataDir": "string",
249
- "exists": true,
250
- "inUse": false,
251
- "pid": "number|null",
252
- "lockFiles": ["string"],
253
- "matchedProcess": { "pid": "number", "name": "string", "cmdline": "string" } | null,
254
- "staleLock": false,
255
- "remotePort": "number|null",
256
- "scope": "default|custom",
257
- "instanceName": "string",
258
- "createdAt": "string|null",
259
- "lastConnect": false
260
- }]
261
- }
262
- ```
263
-
264
- `browser create`:
265
-
266
- ```json
267
- {
268
- "ok": true,
269
- "instance": {
270
- "name": "string",
271
- "browser": "chrome|edge|chromium|brave",
272
- "userDataDir": "string",
273
- "createdAt": "string",
274
- "remoteDebuggingPort": "number|undefined"
275
- }
276
- }
277
- ```
278
-
279
- `browser connect`:
280
-
281
- ```json
282
- {
283
- "ok": true,
284
- "type": "chrome|edge|chromium|brave",
285
- "executablePath": "string",
286
- "userDataDir": "string",
287
- "remoteDebuggingPort": "number",
288
- "wsUrl": "string",
289
- "pid": "number|null"
290
- }
291
- ```
292
-
293
- `browser remove`:
294
-
295
- ```json
296
- {
297
- "ok": true,
298
- "removedInstance": {
299
- "name": "string",
300
- "browser": "chrome|edge|chromium|brave",
301
- "userDataDir": "string",
302
- "createdAt": "string",
303
- "remoteDebuggingPort": "number|undefined"
304
- },
305
- "removedDir": true,
306
- "closedPids": ["number"]
307
- }
308
- ```
309
-
310
- ### Session and account commands
311
-
312
- `status`:
313
-
314
- ```json
315
- {
316
- "ok": true,
317
- "instance": {
318
- "scope": "default|custom",
319
- "name": "string",
320
- "browser": "chrome|edge|chromium|brave",
321
- "source": "argument|last-connect|single-instance",
322
- "status": "running|stopped|missing|stale-lock",
323
- "exists": true,
324
- "inUse": false,
325
- "pid": "number|null",
326
- "remotePort": "number|null",
327
- "userDataDir": "string",
328
- "createdAt": "string|null",
329
- "lastConnect": false
330
- },
331
- "rednote": {
332
- "loginStatus": "logged-in|logged-out|unknown",
333
- "lastLoginAt": "string|null"
334
- }
335
- }
336
- ```
337
-
338
- `check-login`:
339
-
340
- ```json
341
- {
342
- "ok": true,
343
- "rednote": {
344
- "loginStatus": "logged-in|logged-out|unknown",
345
- "lastLoginAt": "string|null",
346
- "needLogin": false,
347
- "checkedAt": "string"
348
- }
349
- }
350
- ```
351
-
352
- `login`:
353
-
354
- ```json
355
- {
356
- "ok": true,
357
- "rednote": {
358
- "loginClicked": true,
359
- "pageUrl": "string",
360
- "waitingForPhoneLogin": true,
361
- "qrCodePath": "string|null",
362
- "message": "string"
363
- }
364
- }
365
- ```
366
-
367
- ### Feed and profile commands
368
-
369
- `home` stdout (both `md` and `json`):
370
-
371
- ```text
372
- id=<database nanoid>
373
- title=<post title>
374
- like=<liked count>
375
- ```
376
-
377
- `home --format json --save PATH` writes the raw `RednotePost[]` array to disk, while stdout still prints the summary list above.
378
-
379
- `search` stdout (both `md` and `json`):
380
-
381
- ```text
382
- id=<database nanoid>
383
- title=<post title>
384
- like=<liked count>
385
- ```
386
-
387
- `search --format json --save PATH` writes the raw `RednotePost[]` array to disk, while stdout still prints the summary list above.
388
-
389
- `get-feed-detail --format json`:
390
-
391
- ```json
392
- {
393
- "ok": true,
394
- "detail": {
395
- "fetchedAt": "string",
396
- "total": "number",
397
- "items": [{
398
- "url": "string",
399
- "note": {
400
- "noteId": "string|null",
401
- "title": "string|null",
402
- "desc": "string|null",
403
- "type": "string|null",
404
- "interactInfo": {
405
- "liked": "boolean|null",
406
- "likedCount": "string|null",
407
- "commentCount": "string|null",
408
- "collected": "boolean|null",
409
- "collectedCount": "string|null",
410
- "shareCount": "string|null",
411
- "followed": "boolean|null"
412
- },
413
- "tagList": [{ "name": "string|null" }],
414
- "imageList": [{ "urlDefault": "string|null", "urlPre": "string|null", "width": "number|null", "height": "number|null" }],
415
- "video": { "url": "string|null", "raw": "unknown" } | null,
416
- "raw": "unknown"
417
- },
418
- "comments": [{
419
- "id": "string|null",
420
- "content": "string|null",
421
- "userId": "string|null",
422
- "nickname": "string|null",
423
- "likedCount": "string|null",
424
- "subCommentCount": "number|null",
425
- "raw": "unknown"
426
- }]
427
- }]
428
- }
429
- }
430
- ```
431
-
432
- `get-profile --format json`:
433
-
434
- ```json
435
- {
436
- "ok": true,
437
- "profile": {
438
- "userId": "string",
439
- "url": "string",
440
- "fetchedAt": "string",
441
- "user": {
442
- "userId": "string|null",
443
- "nickname": "string|null",
444
- "desc": "string|null",
445
- "avatar": "string|null",
446
- "ipLocation": "string|null",
447
- "gender": "string|null",
448
- "follows": "string|number|null",
449
- "fans": "string|number|null",
450
- "interaction": "string|number|null",
451
- "tags": ["string"],
452
- "raw": "unknown"
453
- },
454
- "notes": ["RednotePost"],
455
- "raw": {
456
- "userPageData": "unknown",
457
- "notes": "unknown"
458
- }
459
- }
460
- }
461
- ```
462
-
463
- ### Action commands
464
-
465
- `publish`:
466
-
467
- ```json
468
- {
469
- "ok": true,
470
- "message": "string"
471
- }
472
- ```
473
-
474
- `interact`:
475
-
476
- ```json
477
- {
478
- "ok": true,
479
- "message": "string"
480
- }
481
- ```
482
-
483
175
  ## Storage
484
176
 
485
177
  The CLI stores browser instances and metadata under:
@@ -9,44 +9,70 @@ async function resolveBrowserPid(remoteDebuggingPort, detectedPid) {
9
9
  return portPids[0] ?? null;
10
10
  }
11
11
  export async function resolveConnectOptions(options) {
12
- if (!options.instanceName) {
12
+ if (options.instanceName) {
13
+ if (options.userDataDir) {
14
+ throw new Error('Do not combine --instance with --user-data-dir');
15
+ }
16
+ if (options.browser) {
17
+ throw new Error('Do not combine --instance with --browser');
18
+ }
19
+ const store = readInstanceStore();
20
+ const instanceName = validateInstanceName(options.instanceName);
21
+ const persisted = store.instances.find((instance)=>instance.name === instanceName);
22
+ if (!persisted) {
23
+ throw new Error(`Unknown instance: ${instanceName}`);
24
+ }
25
+ let remoteDebuggingPort = options.remoteDebuggingPort ?? persisted.remoteDebuggingPort;
26
+ if (!remoteDebuggingPort) {
27
+ remoteDebuggingPort = await getRandomAvailablePort();
28
+ updateInstanceRemoteDebuggingPort(instanceName, remoteDebuggingPort);
29
+ }
13
30
  return {
14
- connectOptions: options,
15
- lastConnect: options.userDataDir ? null : {
16
- scope: 'default',
17
- name: options.browser ?? 'chrome',
18
- browser: options.browser ?? 'chrome'
31
+ connectOptions: {
32
+ ...options,
33
+ browser: persisted.browser,
34
+ userDataDir: persisted.userDataDir,
35
+ remoteDebuggingPort
36
+ },
37
+ lastConnect: {
38
+ scope: 'custom',
39
+ name: persisted.name,
40
+ browser: persisted.browser
19
41
  }
20
42
  };
21
43
  }
22
- if (options.userDataDir) {
23
- throw new Error('Do not combine --instance with --user-data-dir');
24
- }
25
- if (options.browser) {
26
- throw new Error('Do not combine --instance with --browser');
27
- }
28
44
  const store = readInstanceStore();
29
- const instanceName = validateInstanceName(options.instanceName);
30
- const persisted = store.instances.find((instance)=>instance.name === instanceName);
31
- if (!persisted) {
32
- throw new Error(`Unknown instance: ${instanceName}`);
33
- }
34
- let remoteDebuggingPort = options.remoteDebuggingPort ?? persisted.remoteDebuggingPort;
35
- if (!remoteDebuggingPort) {
36
- remoteDebuggingPort = await getRandomAvailablePort();
37
- updateInstanceRemoteDebuggingPort(instanceName, remoteDebuggingPort);
45
+ const lastConnect = store.lastConnect;
46
+ if (lastConnect && lastConnect.scope === 'custom') {
47
+ const persisted = store.instances.find((instance)=>instance.name === lastConnect.name);
48
+ if (persisted) {
49
+ let remoteDebuggingPort = options.remoteDebuggingPort ?? persisted.remoteDebuggingPort;
50
+ if (!remoteDebuggingPort) {
51
+ remoteDebuggingPort = await getRandomAvailablePort();
52
+ updateInstanceRemoteDebuggingPort(lastConnect.name, remoteDebuggingPort);
53
+ }
54
+ return {
55
+ connectOptions: {
56
+ ...options,
57
+ instanceName: persisted.name,
58
+ browser: persisted.browser,
59
+ userDataDir: persisted.userDataDir,
60
+ remoteDebuggingPort
61
+ },
62
+ lastConnect: {
63
+ scope: 'custom',
64
+ name: persisted.name,
65
+ browser: persisted.browser
66
+ }
67
+ };
68
+ }
38
69
  }
39
70
  return {
40
- connectOptions: {
41
- ...options,
42
- browser: persisted.browser,
43
- userDataDir: persisted.userDataDir,
44
- remoteDebuggingPort
45
- },
46
- lastConnect: {
47
- scope: 'custom',
48
- name: persisted.name,
49
- browser: persisted.browser
71
+ connectOptions: options,
72
+ lastConnect: options.userDataDir ? null : {
73
+ scope: 'default',
74
+ name: options.browser ?? 'chrome',
75
+ browser: options.browser ?? 'chrome'
50
76
  }
51
77
  };
52
78
  }
@@ -86,6 +112,7 @@ export async function initBrowser(options = {}) {
86
112
  });
87
113
  return {
88
114
  ok: true,
115
+ instanceName: options.instanceName,
89
116
  type: spec.type,
90
117
  executablePath,
91
118
  userDataDir,
@@ -135,6 +162,7 @@ export async function initBrowser(options = {}) {
135
162
  }
136
163
  return {
137
164
  ok: true,
165
+ instanceName: options.instanceName ?? null,
138
166
  type: spec.type,
139
167
  executablePath,
140
168
  userDataDir,
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ Commands:
20
20
  check-login [--instance NAME]
21
21
  login [--instance NAME]
22
22
  publish [--instance NAME]
23
- interact [--instance NAME] --url URL [--like] [--collect] [--comment TEXT]
23
+ interact [--instance NAME] [--id ID | --url URL] [--like] [--collect] [--comment TEXT]
24
24
  home [--instance NAME] [--format md|json] [--save [PATH]]
25
25
  search [--instance NAME] --keyword TEXT [--format md|json] [--save [PATH]]
26
26
  get-feed-detail [--instance NAME] --url URL [--format md|json]
@@ -32,7 +32,7 @@ Examples:
32
32
  npx -y @skills-store/rednote browser connect --instance seller-main
33
33
  npx -y @skills-store/rednote env
34
34
  npx -y @skills-store/rednote publish --instance seller-main --type video --video ./note.mp4 --title 标题 --content 描述
35
- npx -y @skills-store/rednote interact --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect --comment "写得真好"
35
+ npx -y @skills-store/rednote interact --instance seller-main --id NOTE_ID --like --collect --comment "写得真好"
36
36
  npx -y @skills-store/rednote search --instance seller-main --keyword 护肤
37
37
  `);
38
38
  }
@@ -83,8 +83,14 @@ export async function checkRednoteLogin(target, session) {
83
83
  pageUrl: page.url(),
84
84
  needLogin
85
85
  });
86
+ let userId;
87
+ if (!needLogin) {
88
+ const locator = page.locator("li.user a").first();
89
+ userId = (await locator.getAttribute("href"))?.split('/').pop();
90
+ }
86
91
  return {
87
92
  loginStatus: needLogin ? 'logged-out' : 'logged-in',
93
+ userId: userId ?? null,
88
94
  lastLoginAt: null,
89
95
  needLogin,
90
96
  checkedAt: new Date().toISOString()