@skills-store/rednote 0.1.12 → 0.1.14

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
@@ -97,6 +97,16 @@ rednote home --instance seller-main --format md --save
97
97
 
98
98
  Use `home` when you want the current home feed and optionally want to save it to disk.
99
99
 
100
+ The terminal output always uses the compact summary format below, even when `--format json` is selected:
101
+
102
+ ```text
103
+ id=<database nanoid>
104
+ title=<post title>
105
+ like=<liked count>
106
+ ```
107
+
108
+ 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.
109
+
100
110
  ### `search`
101
111
 
102
112
  ```bash
@@ -106,13 +116,26 @@ rednote search --instance seller-main --keyword 护肤 --format json --save ./ou
106
116
 
107
117
  Use `search` for keyword-based note lookup.
108
118
 
119
+ The terminal output always uses the compact summary format below, even when `--format json` is selected:
120
+
121
+ ```text
122
+ id=<database nanoid>
123
+ title=<post title>
124
+ like=<liked count>
125
+ ```
126
+
127
+ Captured search results are also upserted into `~/.skills-router/rednote/main.db` in the `rednote_posts` table. The printed `id` can be passed directly to `get-feed-detail --id`.
128
+
109
129
  ### `get-feed-detail`
110
130
 
111
131
  ```bash
112
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
113
134
  ```
114
135
 
115
- Use `get-feed-detail` when you already have a Xiaohongshu note URL.
136
+ 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.
137
+
138
+ Captured note details and comments are also upserted into `~/.skills-router/rednote/main.db` in `rednote_post_details` and `rednote_post_comments`.
116
139
 
117
140
  ### `get-profile`
118
141
 
@@ -137,9 +160,10 @@ Use `interact` when you want the single entrypoint for note operations such as l
137
160
  - `--instance NAME` selects the browser instance for account-scoped commands.
138
161
  - `--format json` is best for scripting.
139
162
  - `--format md` is best for direct reading.
140
- - `--save` is useful for `home` and `search` when you want saved output.
163
+ - `--save` is useful for `home` and `search` when you want the raw post array written to disk.
141
164
  - `--keyword` is required for `search`.
142
- - `--url` is required for `get-feed-detail`.
165
+ - `home` and `search` always print `id/title/like` summaries to stdout; `--format json` only changes the saved file payload.
166
+ - `get-feed-detail` accepts either `--url URL` or `--id ID`.
143
167
  - `--id` is required for `get-profile`.
144
168
  - `--url` is required for `interact`; at least one of `--like`, `--collect`, or `--comment TEXT` must be provided.
145
169
  - replies are sent with `interact --comment TEXT`.
@@ -203,6 +227,7 @@ Use these shapes as the success model when a command returns JSON.
203
227
  "nodeVersion": "string",
204
228
  "storageHome": "string",
205
229
  "storageRoot": "string",
230
+ "databasePath": "string",
206
231
  "instancesDir": "string",
207
232
  "instanceStorePath": "string",
208
233
  "legacyPackageInstancesDir": "string"
@@ -341,37 +366,26 @@ Use these shapes as the success model when a command returns JSON.
341
366
 
342
367
  ### Feed and profile commands
343
368
 
344
- `home --format json`:
369
+ `home` stdout (both `md` and `json`):
345
370
 
346
- ```json
347
- {
348
- "ok": true,
349
- "home": {
350
- "pageUrl": "string",
351
- "fetchedAt": "string",
352
- "total": "number",
353
- "posts": ["RednotePost"],
354
- "savedPath": "string|undefined"
355
- }
356
- }
371
+ ```text
372
+ id=<database nanoid>
373
+ title=<post title>
374
+ like=<liked count>
357
375
  ```
358
376
 
359
- `search --format json`:
377
+ `home --format json --save PATH` writes the raw `RednotePost[]` array to disk, while stdout still prints the summary list above.
360
378
 
361
- ```json
362
- {
363
- "ok": true,
364
- "search": {
365
- "keyword": "string",
366
- "pageUrl": "string",
367
- "fetchedAt": "string",
368
- "total": "number",
369
- "posts": ["RednotePost"],
370
- "savedPath": "string|undefined"
371
- }
372
- }
379
+ `search` stdout (both `md` and `json`):
380
+
381
+ ```text
382
+ id=<database nanoid>
383
+ title=<post title>
384
+ like=<liked count>
373
385
  ```
374
386
 
387
+ `search --format json --save PATH` writes the raw `RednotePost[]` array to disk, while stdout still prints the summary list above.
388
+
375
389
  `get-feed-detail --format json`:
376
390
 
377
391
  ```json
package/bin/rednote.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { runRootCli } from '../dist/index.js';
3
+ import { stringifyError } from '../dist/utils/browser-cli.js';
4
+
5
+ async function finalizeCliProcess(exitCode) {
6
+ await new Promise((resolve) => process.stdout.write('', () => resolve()));
7
+ await new Promise((resolve) => process.stderr.write('', () => resolve()));
8
+ process.exit(exitCode);
9
+ }
10
+
11
+ try {
12
+ await runRootCli(process.argv.slice(2));
13
+ await finalizeCliProcess(0);
14
+ } catch (error) {
15
+ process.stderr.write(
16
+ `${JSON.stringify(
17
+ {
18
+ ok: false,
19
+ error: stringifyError(error),
20
+ },
21
+ null,
22
+ 2,
23
+ )}\n`,
24
+ );
25
+ await finalizeCliProcess(1);
26
+ }
@@ -28,6 +28,7 @@ function renderEnvironmentMarkdown() {
28
28
  `- Package Root: ${info.packageRoot}`,
29
29
  `- Storage Home: ${info.storageHome}`,
30
30
  `- Storage Root: ${info.storageRoot}`,
31
+ `- Database: ${info.databasePath}`,
31
32
  `- Instances Dir: ${info.instancesDir}`,
32
33
  `- Instance Store: ${info.instanceStorePath}`,
33
34
  `- Legacy Package Instances: ${info.legacyPackageInstancesDir}`,
@@ -3,20 +3,23 @@ import * as cheerio from 'cheerio';
3
3
  import { parseArgs } from 'node:util';
4
4
  import vm from 'node:vm';
5
5
  import { runCli } from '../utils/browser-cli.js';
6
+ import { simulateMouseMove, simulateMousePresence, simulateMouseWheel } from '../utils/mouse-helper.js';
6
7
  import { resolveStatusTarget } from './status.js';
7
8
  import { createRednoteSession, disconnectRednoteSession, ensureRednoteLoggedIn } from './checkLogin.js';
8
9
  import { ensureJsonSavePath, renderJsonSaveSummary, resolveJsonSavePath, writeJsonFile } from './output-format.js';
10
+ import { findPersistedPostUrlByRecordId, initializeRednoteDatabase, persistFeedDetail } from './persistence.js';
9
11
  function printGetFeedDetailHelp() {
10
12
  process.stdout.write(`rednote get-feed-detail
11
13
 
12
14
  Usage:
13
- npx -y @skills-store/rednote get-feed-detail [--instance NAME] --url URL [--url URL] [--comments [COUNT]] [--format md|json] [--save PATH]
14
- node --experimental-strip-types ./scripts/rednote/getFeedDetail.ts --instance NAME --url URL [--url URL] [--comments [COUNT]] [--format md|json] [--save PATH]
15
- bun ./scripts/rednote/getFeedDetail.ts --instance NAME --url URL [--url URL] [--comments [COUNT]] [--format md|json] [--save PATH]
15
+ npx -y @skills-store/rednote get-feed-detail [--instance NAME] [--url URL] [--url URL] [--id ID] [--id ID] [--comments [COUNT]] [--format md|json] [--save PATH]
16
+ node --experimental-strip-types ./scripts/rednote/getFeedDetail.ts --instance NAME [--url URL] [--url URL] [--id ID] [--id ID] [--comments [COUNT]] [--format md|json] [--save PATH]
17
+ bun ./scripts/rednote/getFeedDetail.ts --instance NAME [--url URL] [--url URL] [--id ID] [--id ID] [--comments [COUNT]] [--format md|json] [--save PATH]
16
18
 
17
19
  Options:
18
20
  --instance NAME Optional. Defaults to the saved lastConnect instance
19
- --url URL Required. Xiaohongshu explore url, repeatable
21
+ --url URL Optional. Xiaohongshu explore url, repeatable
22
+ --id ID Optional. Database record id from home/search output, repeatable
20
23
  --comments [COUNT] Optional. Include comment data. When COUNT is provided, scroll \`.note-scroller\` until COUNT comments, the end, or timeout
21
24
  --format FORMAT Output format: md | json. Default: md
22
25
  --save PATH Required when --format json is used. Saves the selected result array as JSON
@@ -46,6 +49,7 @@ function parseCommentsValue(value) {
46
49
  export function parseGetFeedDetailCliArgs(argv) {
47
50
  const values = {
48
51
  urls: [],
52
+ ids: [],
49
53
  format: 'md',
50
54
  comments: undefined,
51
55
  help: false
@@ -80,6 +84,19 @@ export function parseGetFeedDetailCliArgs(argv) {
80
84
  index += 1;
81
85
  continue;
82
86
  }
87
+ if (withEquals?.key === '--id') {
88
+ values.ids.push(withEquals.value);
89
+ continue;
90
+ }
91
+ if (arg === '--id') {
92
+ const nextArg = argv[index + 1];
93
+ if (!nextArg || nextArg.startsWith('-')) {
94
+ throw new Error('Missing required option value: --id');
95
+ }
96
+ values.ids.push(nextArg);
97
+ index += 1;
98
+ continue;
99
+ }
83
100
  if (withEquals?.key === '--format') {
84
101
  values.format = withEquals.value;
85
102
  continue;
@@ -138,20 +155,6 @@ function validateFeedDetailUrl(url) {
138
155
  throw error;
139
156
  }
140
157
  }
141
- function normalizeFeedDetailUrl(url) {
142
- try {
143
- const parsed = new URL(url);
144
- if (!parsed.searchParams.has('xsec_source')) {
145
- parsed.searchParams.set('xsec_source', 'pc_feed');
146
- }
147
- return parsed.toString();
148
- } catch (error) {
149
- if (error instanceof TypeError) {
150
- throw new Error(`url is not valid: ${url}`);
151
- }
152
- throw error;
153
- }
154
- }
155
158
  async function getOrCreateXiaohongshuPage(session) {
156
159
  return session.page;
157
160
  }
@@ -179,7 +182,10 @@ async function scrollCommentsContainer(page, targetCount, getCount) {
179
182
  return;
180
183
  }
181
184
  await container.scrollIntoViewIfNeeded().catch(()=>{});
182
- await container.hover().catch(()=>{});
185
+ await simulateMouseMove(page, {
186
+ locator: container,
187
+ settleMs: 100
188
+ }).catch(()=>{});
183
189
  const getMetrics = async ()=>await container.evaluate((element)=>{
184
190
  const htmlElement = element;
185
191
  const atBottom = htmlElement.scrollTop + htmlElement.clientHeight >= htmlElement.scrollHeight - 8;
@@ -202,8 +208,12 @@ async function scrollCommentsContainer(page, targetCount, getCount) {
202
208
  }
203
209
  const beforeCount = getCount();
204
210
  const delta = Math.max(Math.floor(beforeMetrics.clientHeight * 0.85), 480);
205
- await page.mouse.wheel(0, delta).catch(()=>{});
206
- await page.waitForTimeout(900);
211
+ await simulateMouseWheel(page, {
212
+ locator: container,
213
+ deltaY: delta,
214
+ moveBeforeScroll: false,
215
+ settleMs: 900
216
+ }).catch(()=>{});
207
217
  const afterMetrics = await getMetrics();
208
218
  await page.waitForTimeout(400);
209
219
  const afterCount = getCount();
@@ -298,7 +308,7 @@ function renderDetailMarkdown(items, includeComments = false) {
298
308
  return lines.join('\n');
299
309
  }).join('\n\n---\n\n')}\n`;
300
310
  }
301
- async function captureFeedDetail(page, targetUrl, commentsOption = undefined) {
311
+ async function captureFeedDetail(page, targetUrl, commentsOption = undefined, instanceName) {
302
312
  const includeComments = hasCommentsEnabled(commentsOption);
303
313
  const commentsTarget = typeof commentsOption === 'number' ? commentsOption : null;
304
314
  let note = null;
@@ -342,6 +352,7 @@ async function captureFeedDetail(page, targetUrl, commentsOption = undefined) {
342
352
  await page.goto(targetUrl, {
343
353
  waitUntil: 'domcontentloaded'
344
354
  });
355
+ await simulateMousePresence(page);
345
356
  const deadline = Date.now() + 15_000;
346
357
  while(Date.now() < deadline){
347
358
  if (note && commentsLoaded) {
@@ -355,7 +366,7 @@ async function captureFeedDetail(page, targetUrl, commentsOption = undefined) {
355
366
  if (includeComments && commentsTarget) {
356
367
  await scrollCommentsContainer(page, commentsTarget, ()=>getCommentCount(commentsMap));
357
368
  }
358
- return {
369
+ const item = {
359
370
  url: targetUrl,
360
371
  note: normalizeDetailNote(note),
361
372
  ...includeComments ? {
@@ -364,17 +375,28 @@ async function captureFeedDetail(page, targetUrl, commentsOption = undefined) {
364
375
  ])
365
376
  } : {}
366
377
  };
378
+ if (instanceName) {
379
+ await persistFeedDetail({
380
+ instanceName,
381
+ url: targetUrl,
382
+ note: item.note,
383
+ rawNote: note,
384
+ rawComments: includeComments ? [
385
+ ...commentsMap.values()
386
+ ] : []
387
+ });
388
+ }
389
+ await simulateMousePresence(page);
390
+ return item;
367
391
  } finally{
368
392
  page.off('response', handleResponse);
369
393
  }
370
394
  }
371
- export async function getFeedDetails(session, urls, commentsOption = undefined) {
395
+ export async function getFeedDetails(session, urls, commentsOption = undefined, instanceName) {
372
396
  const page = await getOrCreateXiaohongshuPage(session);
373
397
  const items = [];
374
398
  for (const url of urls){
375
- const normalizedUrl = normalizeFeedDetailUrl(url);
376
- validateFeedDetailUrl(normalizedUrl);
377
- items.push(await captureFeedDetail(page, normalizedUrl, commentsOption));
399
+ items.push(await captureFeedDetail(page, url, commentsOption, instanceName));
378
400
  }
379
401
  return {
380
402
  ok: true,
@@ -385,6 +407,25 @@ export async function getFeedDetails(session, urls, commentsOption = undefined)
385
407
  }
386
408
  };
387
409
  }
410
+ async function resolveFeedDetailUrls(values, instanceName) {
411
+ const urls = [
412
+ ...values.urls
413
+ ];
414
+ if (values.ids.length === 0) {
415
+ return urls;
416
+ }
417
+ if (!instanceName) {
418
+ throw new Error('The --id option requires an instance-backed session.');
419
+ }
420
+ for (const id of values.ids){
421
+ const url = await findPersistedPostUrlByRecordId(instanceName, id);
422
+ if (!url) {
423
+ throw new Error(`No saved post url found for id: ${id}`);
424
+ }
425
+ urls.push(url);
426
+ }
427
+ return urls;
428
+ }
388
429
  function selectFeedDetailOutput(result) {
389
430
  return result.detail.items;
390
431
  }
@@ -400,6 +441,7 @@ function writeFeedDetailOutput(result, values) {
400
441
  }
401
442
  export async function runGetFeedDetailCommand(values = {
402
443
  urls: [],
444
+ ids: [],
403
445
  format: 'md'
404
446
  }) {
405
447
  if (values.help) {
@@ -407,14 +449,16 @@ export async function runGetFeedDetailCommand(values = {
407
449
  return;
408
450
  }
409
451
  ensureJsonSavePath(values.format, values.savePath);
410
- if (values.urls.length === 0) {
411
- throw new Error('Missing required option: --url');
452
+ if (values.urls.length === 0 && values.ids.length === 0) {
453
+ throw new Error('Missing required option: --url or --id');
412
454
  }
455
+ await initializeRednoteDatabase();
413
456
  const target = resolveStatusTarget(values.instance);
414
457
  const session = await createRednoteSession(target);
415
458
  try {
416
459
  await ensureRednoteLoggedIn(target, 'fetching feed detail', session);
417
- const result = await getFeedDetails(session, values.urls, values.comments);
460
+ const urls = await resolveFeedDetailUrls(values, target.instanceName);
461
+ const result = await getFeedDetails(session, urls, values.comments, target.instanceName);
418
462
  writeFeedDetailOutput(result, values);
419
463
  } finally{
420
464
  await disconnectRednoteSession(session);
@@ -121,6 +121,7 @@ function normalizeProfileUser(userPageData) {
121
121
  tags
122
122
  };
123
123
  }
124
+ import { buildExploreUrl, decodeUrlEscapedValue } from './url-format.js';
124
125
  function normalizeProfileNote(item) {
125
126
  const id = firstNonNull(item.id, item.noteId);
126
127
  if (!id) {
@@ -132,12 +133,12 @@ function normalizeProfileNote(item) {
132
133
  const cover = noteCard.cover ?? {};
133
134
  const imageList = Array.isArray(noteCard.imageList ?? noteCard.image_list) ? noteCard.imageList ?? noteCard.image_list : [];
134
135
  const cornerTagInfo = Array.isArray(noteCard.cornerTagInfo ?? noteCard.corner_tag_info) ? noteCard.cornerTagInfo ?? noteCard.corner_tag_info : [];
135
- const xsecToken = firstNonNull(item.xsecToken, item.xsec_token);
136
+ const xsecToken = decodeUrlEscapedValue(firstNonNull(item.xsecToken, item.xsec_token));
136
137
  return {
137
138
  id,
138
139
  modelType: firstNonNull(item.modelType, item.model_type) ?? 'note',
139
140
  xsecToken,
140
- url: xsecToken ? `https://www.xiaohongshu.com/explore/${id}?xsec_token=${xsecToken}` : `https://www.xiaohongshu.com/explore/${id}`,
141
+ url: buildExploreUrl(id, xsecToken),
141
142
  noteCard: {
142
143
  type: firstNonNull(noteCard.type, null),
143
144
  displayTitle: firstNonNull(noteCard.displayTitle, noteCard.display_title),
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from 'node:util';
3
3
  import { runCli } from '../utils/browser-cli.js';
4
+ import { simulateMousePresence } from '../utils/mouse-helper.js';
4
5
  import { resolveStatusTarget } from './status.js';
5
6
  import * as cheerio from 'cheerio';
6
7
  import vm from 'node:vm';
7
- import { ensureJsonSavePath, parseOutputCliArgs, renderJsonSaveSummary, renderPostsMarkdown, resolveJsonSavePath, resolveSavePath, writeJsonFile, writePostsJsonl } from './output-format.js';
8
+ import { ensureJsonSavePath, parseOutputCliArgs, renderPostSummaryList, resolveJsonSavePath, resolveSavePath, writeJsonFile, writePostsJsonl } from './output-format.js';
8
9
  import { createRednoteSession, disconnectRednoteSession } from './checkLogin.js';
10
+ import { initializeRednoteDatabase, listPersistedPostSummaries, persistHomePosts } from './persistence.js';
9
11
  export function parseHomeCliArgs(argv) {
10
12
  return parseOutputCliArgs(argv);
11
13
  }
@@ -32,11 +34,12 @@ function normalizeHomePost(item) {
32
34
  const imageList = Array.isArray(noteCard.imageList) ? noteCard.imageList : [];
33
35
  const cornerTagInfo = Array.isArray(noteCard.cornerTagInfo) ? noteCard.cornerTagInfo : [];
34
36
  const xsecToken = item.xsecToken ?? null;
37
+ const url = xsecToken ? `https://www.xiaohongshu.com/explore/${item.id}?xsec_token=${xsecToken}` : `https://www.xiaohongshu.com/explore/${item.id}`;
35
38
  return {
36
39
  id: item.id,
37
40
  modelType: item.modelType,
38
41
  xsecToken,
39
- url: xsecToken ? `https://www.xiaohongshu.com/explore/${item.id}?xsec_token=${xsecToken}` : `https://www.xiaohongshu.com/explore/${item.id}`,
42
+ url,
40
43
  noteCard: {
41
44
  type: noteCard.type ?? null,
42
45
  displayTitle: noteCard.displayTitle ?? null,
@@ -84,6 +87,20 @@ function normalizeHomePost(item) {
84
87
  }
85
88
  };
86
89
  }
90
+ function buildPostSummaryList(posts, persistedRows = []) {
91
+ const persistedMap = new Map(persistedRows.map((row)=>[
92
+ row.noteId,
93
+ row
94
+ ]));
95
+ return posts.map((post)=>{
96
+ const persisted = persistedMap.get(post.id);
97
+ return {
98
+ id: persisted?.id ?? post.id,
99
+ title: persisted?.title ?? post.noteCard.displayTitle ?? '',
100
+ like: persisted?.likeCount ?? post.noteCard.interactInfo.likedCount ?? ''
101
+ };
102
+ });
103
+ }
87
104
  async function getOrCreateXiaohongshuPage(session) {
88
105
  return session.page;
89
106
  }
@@ -147,20 +164,32 @@ async function collectHomeFeedItems(page) {
147
164
  waitUntil: 'domcontentloaded'
148
165
  });
149
166
  }
167
+ await simulateMousePresence(page);
150
168
  await page.waitForTimeout(500);
151
- return await feedPromise;
169
+ const feedItems = await feedPromise;
170
+ await simulateMousePresence(page);
171
+ return feedItems;
152
172
  }
153
- export async function getRednoteHomePosts(session) {
173
+ export async function getRednoteHomePosts(session, instanceName) {
154
174
  const page = await getOrCreateXiaohongshuPage(session);
155
175
  const items = await collectHomeFeedItems(page);
156
176
  const posts = items.map(normalizeHomePost);
177
+ let summaries = buildPostSummaryList(posts);
178
+ if (instanceName) {
179
+ await persistHomePosts(instanceName, posts.map((post, index)=>({
180
+ post,
181
+ raw: items[index] ?? post
182
+ })));
183
+ summaries = buildPostSummaryList(posts, await listPersistedPostSummaries(instanceName, posts.map((post)=>post.id)));
184
+ }
157
185
  return {
158
186
  ok: true,
159
187
  home: {
160
188
  pageUrl: page.url(),
161
189
  fetchedAt: new Date().toISOString(),
162
190
  total: posts.length,
163
- posts
191
+ posts,
192
+ summaries
164
193
  }
165
194
  };
166
195
  }
@@ -169,21 +198,16 @@ function writeHomeOutput(result, values) {
169
198
  const savedPath = resolveJsonSavePath(values.savePath);
170
199
  result.home.savedPath = savedPath;
171
200
  writeJsonFile(result.home.posts, savedPath);
172
- process.stdout.write(renderJsonSaveSummary(savedPath, result.home.posts));
201
+ process.stdout.write(renderPostSummaryList(result.home.summaries));
173
202
  return;
174
203
  }
175
204
  const posts = result.home.posts;
176
- let savedPath;
177
205
  if (values.saveRequested) {
178
- savedPath = resolveSavePath('home', values.savePath);
206
+ const savedPath = resolveSavePath('home', values.savePath);
179
207
  writePostsJsonl(posts, savedPath);
180
208
  result.home.savedPath = savedPath;
181
209
  }
182
- let markdown = renderPostsMarkdown(posts);
183
- if (savedPath) {
184
- markdown = `Saved JSONL: ${savedPath}\n\n${markdown}`;
185
- }
186
- process.stdout.write(markdown);
210
+ process.stdout.write(renderPostSummaryList(result.home.summaries));
187
211
  }
188
212
  export async function runHomeCommand(values = {
189
213
  format: 'md',
@@ -194,10 +218,11 @@ export async function runHomeCommand(values = {
194
218
  return;
195
219
  }
196
220
  ensureJsonSavePath(values.format, values.savePath);
221
+ await initializeRednoteDatabase();
197
222
  const target = resolveStatusTarget(values.instance);
198
223
  const session = await createRednoteSession(target);
199
224
  try {
200
- const result = await getRednoteHomePosts(session);
225
+ const result = await getRednoteHomePosts(session, target.instanceName);
201
226
  writeHomeOutput(result, values);
202
227
  } finally{
203
228
  await disconnectRednoteSession(session);
@@ -175,6 +175,16 @@ export function renderJsonSaveSummary(filePath, payload) {
175
175
  function formatField(value) {
176
176
  return value ?? '';
177
177
  }
178
+ export function renderPostSummaryList(items) {
179
+ if (items.length === 0) {
180
+ return 'No posts were captured.\n';
181
+ }
182
+ return `${items.map((item)=>[
183
+ `id=${item.id}`,
184
+ `title=${item.title}`,
185
+ `like=${item.like}`
186
+ ].join('\n')).join('\n\n')}\n`;
187
+ }
178
188
  export function renderPostsMarkdown(posts) {
179
189
  if (posts.length === 0) {
180
190
  return 'No posts were captured.\n';