@skills-store/rednote 0.1.0 → 0.1.2

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.
Files changed (42) hide show
  1. package/bin/rednote.js +16 -24
  2. package/dist/browser/connect-browser.js +172 -0
  3. package/dist/browser/create-browser.js +52 -0
  4. package/dist/browser/index.js +35 -0
  5. package/dist/browser/list-browser.js +50 -0
  6. package/dist/browser/remove-browser.js +69 -0
  7. package/{scripts/index.ts → dist/index.js} +19 -25
  8. package/dist/rednote/checkLogin.js +139 -0
  9. package/dist/rednote/env.js +69 -0
  10. package/dist/rednote/getFeedDetail.js +268 -0
  11. package/dist/rednote/getProfile.js +327 -0
  12. package/dist/rednote/home.js +210 -0
  13. package/dist/rednote/index.js +130 -0
  14. package/dist/rednote/login.js +109 -0
  15. package/dist/rednote/output-format.js +116 -0
  16. package/dist/rednote/publish.js +376 -0
  17. package/dist/rednote/search.js +207 -0
  18. package/dist/rednote/status.js +201 -0
  19. package/dist/utils/browser-cli.js +155 -0
  20. package/dist/utils/browser-core.js +705 -0
  21. package/package.json +7 -4
  22. package/scripts/browser/connect-browser.ts +0 -218
  23. package/scripts/browser/create-browser.ts +0 -81
  24. package/scripts/browser/index.ts +0 -49
  25. package/scripts/browser/list-browser.ts +0 -74
  26. package/scripts/browser/remove-browser.ts +0 -109
  27. package/scripts/rednote/checkLogin.ts +0 -171
  28. package/scripts/rednote/env.ts +0 -79
  29. package/scripts/rednote/getFeedDetail.ts +0 -351
  30. package/scripts/rednote/getProfile.ts +0 -420
  31. package/scripts/rednote/home.ts +0 -316
  32. package/scripts/rednote/index.ts +0 -122
  33. package/scripts/rednote/login.ts +0 -142
  34. package/scripts/rednote/output-format.ts +0 -156
  35. package/scripts/rednote/post-types.ts +0 -51
  36. package/scripts/rednote/search.ts +0 -316
  37. package/scripts/rednote/status.ts +0 -280
  38. package/scripts/utils/browser-cli.ts +0 -176
  39. package/scripts/utils/browser-core.ts +0 -906
  40. package/tsconfig.json +0 -13
  41. /package/{scripts/rednote/collect.ts → dist/rednote/collect.js} +0 -0
  42. /package/{scripts/rednote/publish.ts → dist/rednote/post-types.js} +0 -0
package/bin/rednote.js CHANGED
@@ -1,27 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from 'node:child_process';
3
- import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
2
+ import { runRootCli } from '../dist/index.js';
3
+ import { stringifyError } from '../dist/utils/browser-cli.js';
5
4
 
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
8
- const entry = path.resolve(__dirname, '../scripts/index.ts');
9
-
10
- const child = spawn(process.execPath, ['--experimental-strip-types', entry, ...process.argv.slice(2)], {
11
- stdio: 'inherit',
12
- env: process.env,
13
- });
14
-
15
- child.on('exit', (code, signal) => {
16
- if (signal) {
17
- process.kill(process.pid, signal);
18
- return;
19
- }
20
-
21
- process.exit(code ?? 1);
22
- });
23
-
24
- child.on('error', (error) => {
25
- console.error(error instanceof Error ? error.message : String(error));
5
+ try {
6
+ await runRootCli(process.argv.slice(2));
7
+ } catch (error) {
8
+ process.stderr.write(
9
+ `${JSON.stringify(
10
+ {
11
+ ok: false,
12
+ error: stringifyError(error),
13
+ },
14
+ null,
15
+ 2,
16
+ )}\n`,
17
+ );
26
18
  process.exit(1);
27
- });
19
+ }
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ import { closeProcessesByPid, findSpec, getCdpWebSocketUrl, getPidsListeningOnPort, inspectBrowserInstance, getRandomAvailablePort, launchBrowser, removeLockFiles, readInstanceStore, resolveExecutablePath, resolveUserDataDir, updateInstanceRemoteDebuggingPort, updateLastConnect, validateInstanceName, waitForCdpReady, waitForPortToClose } from '../utils/browser-core.js';
3
+ import { parseBrowserCliArgs, printConnectBrowserHelp, printJson, runCli, debugLog } from '../utils/browser-cli.js';
4
+ async function resolveBrowserPid(remoteDebuggingPort, detectedPid) {
5
+ if (detectedPid) {
6
+ return detectedPid;
7
+ }
8
+ const portPids = await getPidsListeningOnPort(remoteDebuggingPort);
9
+ return portPids[0] ?? null;
10
+ }
11
+ export async function resolveConnectOptions(options) {
12
+ if (!options.instanceName) {
13
+ return {
14
+ connectOptions: options,
15
+ lastConnect: options.userDataDir ? null : {
16
+ scope: 'default',
17
+ name: options.browser ?? 'chrome',
18
+ browser: options.browser ?? 'chrome'
19
+ }
20
+ };
21
+ }
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
+ 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);
38
+ }
39
+ 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
50
+ }
51
+ };
52
+ }
53
+ export async function initBrowser(options = {}) {
54
+ debugLog('initBrowser', 'start', {
55
+ options
56
+ });
57
+ const browserType = options.browser ?? 'chrome';
58
+ const spec = findSpec(browserType);
59
+ const executablePath = options.executablePath || resolveExecutablePath(spec);
60
+ if (!executablePath) {
61
+ throw new Error(`No executable found for ${spec.displayName}`);
62
+ }
63
+ const userDataDir = resolveUserDataDir(spec, options);
64
+ const remoteDebuggingPort = options.remoteDebuggingPort ?? 9222;
65
+ const connectTimeoutMs = options.connectTimeoutMs ?? 15_000;
66
+ const killTimeoutMs = options.killTimeoutMs ?? 8_000;
67
+ const startupUrl = options.startupUrl ?? 'about:blank';
68
+ const detectedInstance = await inspectBrowserInstance(spec, executablePath, userDataDir);
69
+ debugLog('initBrowser', 'inspected instance', {
70
+ browserType,
71
+ executablePath,
72
+ userDataDir,
73
+ remoteDebuggingPort,
74
+ detectedInstance
75
+ });
76
+ const closedPidSet = new Set();
77
+ const existingWsUrl = await getCdpWebSocketUrl(remoteDebuggingPort);
78
+ debugLog('initBrowser', 'checked existing ws url', {
79
+ remoteDebuggingPort,
80
+ existingWsUrl
81
+ });
82
+ if (existingWsUrl) {
83
+ debugLog('initBrowser', 'reusing existing ws url', {
84
+ remoteDebuggingPort,
85
+ existingWsUrl
86
+ });
87
+ return {
88
+ ok: true,
89
+ type: spec.type,
90
+ executablePath,
91
+ userDataDir,
92
+ remoteDebuggingPort,
93
+ wsUrl: existingWsUrl,
94
+ pid: await resolveBrowserPid(remoteDebuggingPort, detectedInstance.pid)
95
+ };
96
+ }
97
+ if (detectedInstance.inUse && detectedInstance.remotePort === null) {
98
+ if (!options.force) {
99
+ throw new Error(`Browser profile is already in use without a remote debugging port. Re-run with --force to safely close it first: ${detectedInstance.userDataDir}`);
100
+ }
101
+ if (detectedInstance.pids.length === 0) {
102
+ throw new Error(`Browser profile is in use but no running browser PID was found for safe shutdown: ${detectedInstance.userDataDir}`);
103
+ }
104
+ }
105
+ if (detectedInstance.inUse && detectedInstance.pids.length > 0) {
106
+ debugLog('initBrowser', 'detected in-use profile, closing existing processes', {
107
+ pids: detectedInstance.pids,
108
+ killTimeoutMs,
109
+ remoteDebuggingPort
110
+ });
111
+ for (const pid of (await closeProcessesByPid(detectedInstance.pids, killTimeoutMs))){
112
+ closedPidSet.add(pid);
113
+ }
114
+ await waitForPortToClose(remoteDebuggingPort, killTimeoutMs);
115
+ } else if (detectedInstance.staleLock) {
116
+ if (!options.force) {
117
+ throw new Error(`Profile is locked by stale files: ${detectedInstance.lockFiles.join(', ')}`);
118
+ }
119
+ removeLockFiles(detectedInstance.lockFiles);
120
+ }
121
+ const launched = await launchBrowser(spec, executablePath, userDataDir, remoteDebuggingPort, startupUrl);
122
+ debugLog('initBrowser', 'launched browser', {
123
+ remoteDebuggingPort,
124
+ pid: launched.pid ?? null,
125
+ startupUrl
126
+ });
127
+ const wsUrl = await waitForCdpReady(remoteDebuggingPort, connectTimeoutMs);
128
+ debugLog('initBrowser', 'waited for cdp ready', {
129
+ remoteDebuggingPort,
130
+ connectTimeoutMs,
131
+ wsUrl
132
+ });
133
+ if (!wsUrl) {
134
+ throw new Error(`CDP did not become available on port ${remoteDebuggingPort}`);
135
+ }
136
+ return {
137
+ ok: true,
138
+ type: spec.type,
139
+ executablePath,
140
+ userDataDir,
141
+ remoteDebuggingPort,
142
+ wsUrl,
143
+ pid: launched.pid ?? await resolveBrowserPid(remoteDebuggingPort, detectedInstance.pid)
144
+ };
145
+ }
146
+ export async function runConnectBrowserCommand(values) {
147
+ const resolved = await resolveConnectOptions({
148
+ browser: values.browser,
149
+ instanceName: values.instance,
150
+ executablePath: values['executable-path'],
151
+ userDataDir: values['user-data-dir'],
152
+ force: values.force,
153
+ remoteDebuggingPort: values.port ? Number(values.port) : undefined,
154
+ connectTimeoutMs: values.timeout ? Number(values.timeout) : undefined,
155
+ killTimeoutMs: values['kill-timeout'] ? Number(values['kill-timeout']) : undefined,
156
+ startupUrl: values['startup-url']
157
+ });
158
+ const result = await initBrowser(resolved.connectOptions);
159
+ if (resolved.lastConnect) {
160
+ updateLastConnect(resolved.lastConnect);
161
+ }
162
+ printJson(result);
163
+ }
164
+ async function main() {
165
+ const { values } = parseBrowserCliArgs(process.argv.slice(2));
166
+ if (values.help) {
167
+ printConnectBrowserHelp();
168
+ return;
169
+ }
170
+ await runConnectBrowserCommand(values);
171
+ }
172
+ runCli(import.meta.url, main);
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ import { customInstanceUserDataDir, ensureDir, isDefaultInstanceName, readInstanceStore, validateInstanceName, writeInstanceStore } from '../utils/browser-core.js';
3
+ import { parseBrowserCliArgs, printCreateBrowserHelp, printJson, runCli } from '../utils/browser-cli.js';
4
+ export function createBrowserInstance(options) {
5
+ const store = readInstanceStore();
6
+ const name = validateInstanceName(options.name);
7
+ const browser = options.browser ?? 'chrome';
8
+ if (isDefaultInstanceName(name)) {
9
+ throw new Error(`Instance name is reserved for a default browser: ${name}`);
10
+ }
11
+ if (store.instances.some((instance)=>instance.name === name)) {
12
+ throw new Error(`Instance already exists: ${name}`);
13
+ }
14
+ const instance = {
15
+ name,
16
+ browser,
17
+ userDataDir: ensureDir(customInstanceUserDataDir(name)),
18
+ createdAt: new Date().toISOString(),
19
+ remoteDebuggingPort: typeof options.remoteDebuggingPort === 'number' && Number.isInteger(options.remoteDebuggingPort) && options.remoteDebuggingPort > 0 ? options.remoteDebuggingPort : undefined
20
+ };
21
+ writeInstanceStore({
22
+ ...store,
23
+ instances: [
24
+ ...store.instances,
25
+ instance
26
+ ]
27
+ });
28
+ return {
29
+ ok: true,
30
+ instance
31
+ };
32
+ }
33
+ export function runCreateBrowserCommand(values) {
34
+ if (!values.name) {
35
+ throw new Error('Missing required option: --name');
36
+ }
37
+ const result = createBrowserInstance({
38
+ name: values.name,
39
+ browser: values.browser,
40
+ remoteDebuggingPort: values.port ? Number(values.port) : undefined
41
+ });
42
+ printJson(result);
43
+ }
44
+ async function main() {
45
+ const { values } = parseBrowserCliArgs(process.argv.slice(2));
46
+ if (values.help) {
47
+ printCreateBrowserHelp();
48
+ return;
49
+ }
50
+ runCreateBrowserCommand(values);
51
+ }
52
+ runCli(import.meta.url, main);
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import { parseBrowserCliArgs, printInitBrowserHelp, runCli } from '../utils/browser-cli.js';
3
+ export async function runBrowserCli(argv = process.argv.slice(2)) {
4
+ const { values, positionals } = parseBrowserCliArgs(argv);
5
+ const command = positionals[0] || 'list';
6
+ if (values.help || command === 'help') {
7
+ printInitBrowserHelp();
8
+ return;
9
+ }
10
+ if (command === 'list') {
11
+ const { runListBrowserCommand } = await import('./list-browser.js');
12
+ await runListBrowserCommand(values);
13
+ return;
14
+ }
15
+ if (command === 'create') {
16
+ const { runCreateBrowserCommand } = await import('./create-browser.js');
17
+ runCreateBrowserCommand(values);
18
+ return;
19
+ }
20
+ if (command === 'remove') {
21
+ const { runRemoveBrowserCommand } = await import('./remove-browser.js');
22
+ await runRemoveBrowserCommand(values);
23
+ return;
24
+ }
25
+ if (command === 'connect') {
26
+ const { runConnectBrowserCommand } = await import('./connect-browser.js');
27
+ await runConnectBrowserCommand(values);
28
+ return;
29
+ }
30
+ throw new Error(`Unknown browser command: ${command}`);
31
+ }
32
+ async function main() {
33
+ await runBrowserCli();
34
+ }
35
+ runCli(import.meta.url, main);
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import { browserSpecs, findSpec, inspectBrowserInstance, isLastConnectMatch, readInstanceStore, toBrowserInstanceInfo } from '../utils/browser-core.js';
3
+ import { parseBrowserCliArgs, printJson, printListBrowserHelp, runCli } from '../utils/browser-cli.js';
4
+ export async function listBrowserInstances() {
5
+ const specs = browserSpecs();
6
+ const store = readInstanceStore();
7
+ const defaultInstances = await Promise.all(specs.map(async (spec)=>{
8
+ const instance = await inspectBrowserInstance(spec);
9
+ return {
10
+ ...toBrowserInstanceInfo(instance),
11
+ scope: 'default',
12
+ instanceName: spec.type,
13
+ createdAt: null,
14
+ lastConnect: isLastConnectMatch(store.lastConnect, 'default', spec.type, spec.type)
15
+ };
16
+ }));
17
+ const customInstances = await Promise.all(store.instances.map(async (persisted)=>{
18
+ const spec = findSpec(persisted.browser);
19
+ const instance = await inspectBrowserInstance(spec, undefined, persisted.userDataDir);
20
+ return {
21
+ ...toBrowserInstanceInfo(instance),
22
+ name: persisted.name,
23
+ userDataDir: persisted.userDataDir,
24
+ scope: 'custom',
25
+ instanceName: persisted.name,
26
+ createdAt: persisted.createdAt,
27
+ lastConnect: isLastConnectMatch(store.lastConnect, 'custom', persisted.name, persisted.browser)
28
+ };
29
+ }));
30
+ return {
31
+ lastConnect: store.lastConnect,
32
+ instances: [
33
+ ...defaultInstances,
34
+ ...customInstances
35
+ ]
36
+ };
37
+ }
38
+ export async function runListBrowserCommand(_values) {
39
+ const instances = await listBrowserInstances();
40
+ printJson(instances);
41
+ }
42
+ async function main() {
43
+ const { values } = parseBrowserCliArgs(process.argv.slice(2));
44
+ if (values.help) {
45
+ printListBrowserHelp();
46
+ return;
47
+ }
48
+ await runListBrowserCommand(values);
49
+ }
50
+ runCli(import.meta.url, main);
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import { closeProcessesByPid, customInstanceUserDataDir, exists, findSpec, inspectBrowserInstance, INSTANCES_DIR, isDefaultInstanceName, isSubPath, normalizePath, readInstanceStore, validateInstanceName, waitForPortToClose, writeInstanceStore } from '../utils/browser-core.js';
4
+ import { parseBrowserCliArgs, printJson, printRemoveBrowserHelp, runCli } from '../utils/browser-cli.js';
5
+ export async function removeBrowserInstance(options) {
6
+ const store = readInstanceStore();
7
+ const name = validateInstanceName(options.name);
8
+ if (isDefaultInstanceName(name)) {
9
+ throw new Error(`Default instance cannot be removed: ${name}`);
10
+ }
11
+ const instance = store.instances.find((item)=>item.name === name);
12
+ if (!instance) {
13
+ throw new Error(`Instance not found: ${name}`);
14
+ }
15
+ const expectedUserDataDir = customInstanceUserDataDir(name);
16
+ const resolvedUserDataDir = normalizePath(instance.userDataDir);
17
+ if (!isSubPath(INSTANCES_DIR, resolvedUserDataDir) || normalizePath(expectedUserDataDir) !== resolvedUserDataDir) {
18
+ throw new Error(`Refusing to remove unexpected instance directory: ${instance.userDataDir}`);
19
+ }
20
+ const spec = findSpec(instance.browser);
21
+ const detectedInstance = await inspectBrowserInstance(spec, undefined, resolvedUserDataDir);
22
+ if (detectedInstance.inUse && !options.force) {
23
+ throw new Error(`Instance is currently in use. Re-run with --force: ${name}`);
24
+ }
25
+ const closedPids = [];
26
+ if (options.force && detectedInstance.pids.length > 0) {
27
+ for (const pid of (await closeProcessesByPid(detectedInstance.pids, 8_000))){
28
+ closedPids.push(pid);
29
+ }
30
+ if (detectedInstance.remotePort !== null) {
31
+ await waitForPortToClose(detectedInstance.remotePort, 8_000);
32
+ }
33
+ }
34
+ const removedDir = exists(resolvedUserDataDir);
35
+ fs.rmSync(resolvedUserDataDir, {
36
+ recursive: true,
37
+ force: true
38
+ });
39
+ writeInstanceStore({
40
+ ...store,
41
+ lastConnect: store.lastConnect?.scope === 'custom' && store.lastConnect.name === instance.name && store.lastConnect.browser === instance.browser ? null : store.lastConnect,
42
+ instances: store.instances.filter((item)=>item.name !== instance.name)
43
+ });
44
+ return {
45
+ ok: true,
46
+ removedInstance: instance,
47
+ removedDir,
48
+ closedPids
49
+ };
50
+ }
51
+ export async function runRemoveBrowserCommand(values) {
52
+ if (!values.name) {
53
+ throw new Error('Missing required option: --name');
54
+ }
55
+ const result = await removeBrowserInstance({
56
+ name: values.name,
57
+ force: values.force
58
+ });
59
+ printJson(result);
60
+ }
61
+ async function main() {
62
+ const { values } = parseBrowserCliArgs(process.argv.slice(2));
63
+ if (values.help) {
64
+ process.stdout.write(`remove-browser\n\nUsage:\n node --experimental-strip-types ./scripts/browser/remove-browser.ts --name NAME [--force]\n bun ./scripts/browser/remove-browser.ts --name NAME [--force]\n`);
65
+ return;
66
+ }
67
+ await runRemoveBrowserCommand(values);
68
+ }
69
+ runCli(import.meta.url, main);
@@ -1,9 +1,7 @@
1
- #!/usr/bin/env -S node --experimental-strip-types
2
-
3
- import { runCli } from './utils/browser-cli.ts';
4
-
1
+ #!/usr/bin/env node
2
+ import { runCli } from './utils/browser-cli.js';
5
3
  function printRootHelp() {
6
- process.stdout.write(`rednote
4
+ process.stdout.write(`rednote
7
5
 
8
6
  Usage:
9
7
  npx -y @skills-store/rednote browser <command> [...args]
@@ -15,6 +13,7 @@ Commands:
15
13
  status [--instance NAME]
16
14
  check-login [--instance NAME]
17
15
  login [--instance NAME]
16
+ publish [--instance NAME]
18
17
  home [--instance NAME] [--format md|json] [--save [PATH]]
19
18
  search [--instance NAME] --keyword TEXT [--format md|json] [--save [PATH]]
20
19
  get-feed-detail [--instance NAME] --url URL [--format md|json]
@@ -25,30 +24,25 @@ Examples:
25
24
  npx -y @skills-store/rednote browser create --name seller-main --browser chrome
26
25
  npx -y @skills-store/rednote browser connect --instance seller-main
27
26
  npx -y @skills-store/rednote env
27
+ npx -y @skills-store/rednote publish --instance seller-main --type video --video ./note.mp4 --title 标题 --content 描述
28
28
  npx -y @skills-store/rednote search --instance seller-main --keyword 护肤
29
29
  `);
30
30
  }
31
-
32
- export async function runRootCli(argv: string[] = process.argv.slice(2)) {
33
- const [command, ...restArgv] = argv;
34
-
35
- if (!command || command === 'help' || command === '--help' || command === '-h') {
36
- printRootHelp();
37
- return;
38
- }
39
-
40
- if (command === 'browser') {
41
- const { runBrowserCli } = await import('./browser/index.ts');
42
- await runBrowserCli(restArgv);
43
- return;
44
- }
45
-
46
- const { runRednoteCli } = await import('./rednote/index.ts');
47
- await runRednoteCli(argv);
31
+ export async function runRootCli(argv = process.argv.slice(2)) {
32
+ const [command, ...restArgv] = argv;
33
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
34
+ printRootHelp();
35
+ return;
36
+ }
37
+ if (command === 'browser') {
38
+ const { runBrowserCli } = await import('./browser/index.js');
39
+ await runBrowserCli(restArgv);
40
+ return;
41
+ }
42
+ const { runRednoteCli } = await import('./rednote/index.js');
43
+ await runRednoteCli(argv);
48
44
  }
49
-
50
45
  async function main() {
51
- await runRootCli();
46
+ await runRootCli();
52
47
  }
53
-
54
48
  runCli(import.meta.url, main);
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util';
3
+ import { initBrowser, resolveConnectOptions } from '../browser/connect-browser.js';
4
+ import { connectOverCdp, updateLastConnect } from '../utils/browser-core.js';
5
+ import { debugLog, printJson, runCli } from '../utils/browser-cli.js';
6
+ import { resolveStatusTarget } from './status.js';
7
+ function printCheckLoginHelp() {
8
+ process.stdout.write(`rednote check-login
9
+
10
+ Usage:
11
+ npx -y @skills-store/rednote check-login [--instance NAME]
12
+ node --experimental-strip-types ./scripts/rednote/checkLogin.ts --instance NAME
13
+ bun ./scripts/rednote/checkLogin.ts --instance NAME
14
+
15
+ Options:
16
+ --instance NAME Optional. Defaults to the saved lastConnect instance
17
+ -h, --help Show this help
18
+ `);
19
+ }
20
+ export async function createRednoteSession(target) {
21
+ debugLog('checkLogin', 'create session start', {
22
+ target
23
+ });
24
+ const resolved = await resolveConnectOptions(target.scope === 'custom' ? {
25
+ instanceName: target.instanceName
26
+ } : {
27
+ browser: target.browser
28
+ });
29
+ const launched = await initBrowser(resolved.connectOptions);
30
+ debugLog('checkLogin', 'initBrowser resolved', {
31
+ target,
32
+ connectOptions: resolved.connectOptions,
33
+ launched
34
+ });
35
+ if (resolved.lastConnect) {
36
+ updateLastConnect(resolved.lastConnect);
37
+ }
38
+ const browser = await connectOverCdp(launched.wsUrl);
39
+ debugLog('checkLogin', 'connected over cdp', {
40
+ wsUrl: launched.wsUrl,
41
+ remoteDebuggingPort: launched.remoteDebuggingPort
42
+ });
43
+ const browserContext = browser.contexts()[0];
44
+ if (!browserContext) {
45
+ throw new Error(`No browser context found for instance: ${target.instanceName}`);
46
+ }
47
+ const page = browserContext.pages().find((candidate)=>candidate.url().startsWith('https://www.xiaohongshu.com/')) ?? await browserContext.newPage();
48
+ debugLog('checkLogin', 'session page resolved', {
49
+ pageUrl: page.url(),
50
+ totalPages: browserContext.pages().length
51
+ });
52
+ return {
53
+ browser,
54
+ browserContext,
55
+ page
56
+ };
57
+ }
58
+ export function disconnectRednoteSession(session) {
59
+ try {
60
+ session.browser._connection?.close();
61
+ } catch {}
62
+ }
63
+ export async function checkRednoteLogin(target, session) {
64
+ const ownsSession = !session;
65
+ const activeSession = session ?? await createRednoteSession(target);
66
+ try {
67
+ debugLog('checkLogin', 'check login start', {
68
+ target,
69
+ reusedSession: Boolean(session)
70
+ });
71
+ const page = activeSession.page;
72
+ if (!page.url().startsWith('https://www.xiaohongshu.com/')) {
73
+ debugLog('checkLogin', 'page is not on xiaohongshu, navigating', {
74
+ currentUrl: page.url()
75
+ });
76
+ await page.goto('https://www.xiaohongshu.com/explore', {
77
+ waitUntil: 'domcontentloaded'
78
+ });
79
+ }
80
+ await page.waitForTimeout(2_000);
81
+ const needLogin = await page.locator('#login-btn').count() > 0;
82
+ debugLog('checkLogin', 'login state checked', {
83
+ pageUrl: page.url(),
84
+ needLogin
85
+ });
86
+ return {
87
+ loginStatus: needLogin ? 'logged-out' : 'logged-in',
88
+ lastLoginAt: null,
89
+ needLogin,
90
+ checkedAt: new Date().toISOString()
91
+ };
92
+ } finally{
93
+ if (ownsSession) {
94
+ disconnectRednoteSession(activeSession);
95
+ }
96
+ }
97
+ }
98
+ export async function ensureRednoteLoggedIn(target, action = 'continue', session) {
99
+ const rednote = await checkRednoteLogin(target, session);
100
+ if (rednote.needLogin) {
101
+ throw new Error(`Xiaohongshu login is required before ${action}. Run \`rednote login --instance ${target.instanceName}\` first.`);
102
+ }
103
+ return rednote;
104
+ }
105
+ export async function runCheckLoginCommand(values = {}) {
106
+ if (values.help) {
107
+ printCheckLoginHelp();
108
+ return;
109
+ }
110
+ const target = resolveStatusTarget(values.instance);
111
+ const rednote = await checkRednoteLogin(target);
112
+ const result = {
113
+ ok: true,
114
+ rednote
115
+ };
116
+ printJson(result);
117
+ }
118
+ async function main() {
119
+ const { values } = parseArgs({
120
+ args: process.argv.slice(2),
121
+ allowPositionals: true,
122
+ strict: false,
123
+ options: {
124
+ instance: {
125
+ type: 'string'
126
+ },
127
+ help: {
128
+ type: 'boolean',
129
+ short: 'h'
130
+ }
131
+ }
132
+ });
133
+ if (values.help) {
134
+ printCheckLoginHelp();
135
+ return;
136
+ }
137
+ await runCheckLoginCommand(values);
138
+ }
139
+ runCli(import.meta.url, main);