@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/package.json CHANGED
@@ -1,18 +1,21 @@
1
1
  {
2
2
  "name": "@skills-store/rednote",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "rednote": "./bin/rednote.js"
8
8
  },
9
9
  "scripts": {
10
+ "build": "node ./build.mjs",
11
+ "prepack": "npm run build",
10
12
  "rednote": "node --experimental-strip-types ./scripts/index.ts",
11
13
  "browser": "node --experimental-strip-types ./scripts/browser/index.ts",
12
14
  "env": "node --experimental-strip-types ./scripts/rednote/env.ts",
13
15
  "status": "node --experimental-strip-types ./scripts/rednote/status.ts",
14
16
  "check-login": "node --experimental-strip-types ./scripts/rednote/checkLogin.ts",
15
17
  "login": "node --experimental-strip-types ./scripts/rednote/login.ts",
18
+ "publish": "node --experimental-strip-types ./scripts/rednote/publish.ts",
16
19
  "home": "node --experimental-strip-types ./scripts/rednote/home.ts",
17
20
  "search": "node --experimental-strip-types ./scripts/rednote/search.ts",
18
21
  "get-feed-detail": "node --experimental-strip-types ./scripts/rednote/getFeedDetail.ts",
@@ -23,6 +26,7 @@
23
26
  "bun:status": "bun ./scripts/rednote/status.ts",
24
27
  "bun:check-login": "bun ./scripts/rednote/checkLogin.ts",
25
28
  "bun:login": "bun ./scripts/rednote/login.ts",
29
+ "bun:publish": "bun ./scripts/rednote/publish.ts",
26
30
  "bun:home": "bun ./scripts/rednote/home.ts",
27
31
  "bun:search": "bun ./scripts/rednote/search.ts",
28
32
  "bun:get-feed-detail": "bun ./scripts/rednote/getFeedDetail.ts",
@@ -46,9 +50,8 @@
46
50
  },
47
51
  "files": [
48
52
  "bin/rednote.js",
49
- "scripts",
53
+ "dist",
50
54
  "README.md",
51
- "package.json",
52
- "tsconfig.json"
55
+ "package.json"
53
56
  ]
54
57
  }
@@ -1,218 +0,0 @@
1
- #!/usr/bin/env -S node --experimental-strip-types
2
-
3
- import {
4
- closeProcessesByPid,
5
- findSpec,
6
- getCdpWebSocketUrl,
7
- getPidsListeningOnPort,
8
- inspectBrowserInstance,
9
- getRandomAvailablePort,
10
- launchBrowser,
11
- removeLockFiles,
12
- readInstanceStore,
13
- resolveExecutablePath,
14
- resolveUserDataDir,
15
- type ConnectBrowserOptions,
16
- type ConnectBrowserResult,
17
- type LastConnectInfo,
18
- updateInstanceRemoteDebuggingPort,
19
- updateLastConnect,
20
- validateInstanceName,
21
- waitForCdpReady,
22
- waitForPortToClose,
23
- } from '../utils/browser-core.ts';
24
- import {
25
- parseBrowserCliArgs,
26
- printConnectBrowserHelp,
27
- printJson,
28
- runCli,
29
- debugLog,
30
- type BrowserCliValues,
31
- } from '../utils/browser-cli.ts';
32
-
33
-
34
- async function resolveBrowserPid(remoteDebuggingPort: number, detectedPid: number | null) {
35
- if (detectedPid) {
36
- return detectedPid;
37
- }
38
-
39
- const portPids = await getPidsListeningOnPort(remoteDebuggingPort);
40
- return portPids[0] ?? null;
41
- }
42
-
43
- export async function resolveConnectOptions(options: ConnectBrowserOptions & { instanceName?: string }) {
44
- if (!options.instanceName) {
45
- return {
46
- connectOptions: options,
47
- lastConnect:
48
- options.userDataDir
49
- ? null
50
- : {
51
- scope: 'default',
52
- name: options.browser ?? 'chrome',
53
- browser: options.browser ?? 'chrome',
54
- } satisfies LastConnectInfo,
55
- };
56
- }
57
-
58
- if (options.userDataDir) {
59
- throw new Error('Do not combine --instance with --user-data-dir');
60
- }
61
-
62
- if (options.browser) {
63
- throw new Error('Do not combine --instance with --browser');
64
- }
65
-
66
- const store = readInstanceStore();
67
- const instanceName = validateInstanceName(options.instanceName);
68
- const persisted = store.instances.find((instance) => instance.name === instanceName);
69
- if (!persisted) {
70
- throw new Error(`Unknown instance: ${instanceName}`);
71
- }
72
-
73
- let remoteDebuggingPort = options.remoteDebuggingPort ?? persisted.remoteDebuggingPort;
74
- if (!remoteDebuggingPort) {
75
- remoteDebuggingPort = await getRandomAvailablePort();
76
- updateInstanceRemoteDebuggingPort(instanceName, remoteDebuggingPort);
77
- }
78
-
79
- return {
80
- connectOptions: {
81
- ...options,
82
- browser: persisted.browser,
83
- userDataDir: persisted.userDataDir,
84
- remoteDebuggingPort,
85
- } satisfies ConnectBrowserOptions,
86
- lastConnect: {
87
- scope: 'custom',
88
- name: persisted.name,
89
- browser: persisted.browser,
90
- } satisfies LastConnectInfo,
91
- };
92
- }
93
-
94
- export async function initBrowser(options: ConnectBrowserOptions = {}): Promise<ConnectBrowserResult> {
95
- debugLog('initBrowser', 'start', { options });
96
- const browserType = options.browser ?? 'chrome';
97
- const spec = findSpec(browserType);
98
- const executablePath = options.executablePath || resolveExecutablePath(spec);
99
- if (!executablePath) {
100
- throw new Error(`No executable found for ${spec.displayName}`);
101
- }
102
-
103
- const userDataDir = resolveUserDataDir(spec, options);
104
- const remoteDebuggingPort = options.remoteDebuggingPort ?? 9222;
105
- const connectTimeoutMs = options.connectTimeoutMs ?? 15_000;
106
- const killTimeoutMs = options.killTimeoutMs ?? 8_000;
107
- const startupUrl = options.startupUrl ?? 'about:blank';
108
-
109
- const detectedInstance = await inspectBrowserInstance(spec, executablePath, userDataDir);
110
- debugLog('initBrowser', 'inspected instance', {
111
- browserType,
112
- executablePath,
113
- userDataDir,
114
- remoteDebuggingPort,
115
- detectedInstance,
116
- });
117
- const closedPidSet = new Set<number>();
118
-
119
- const existingWsUrl = await getCdpWebSocketUrl(remoteDebuggingPort);
120
- debugLog('initBrowser', 'checked existing ws url', { remoteDebuggingPort, existingWsUrl });
121
- if (existingWsUrl) {
122
- debugLog('initBrowser', 'reusing existing ws url', { remoteDebuggingPort, existingWsUrl });
123
- return {
124
- ok: true,
125
- type: spec.type,
126
- executablePath,
127
- userDataDir,
128
- remoteDebuggingPort,
129
- wsUrl: existingWsUrl,
130
- pid: await resolveBrowserPid(remoteDebuggingPort, detectedInstance.pid),
131
- };
132
- }
133
-
134
- if (detectedInstance.inUse && detectedInstance.remotePort === null) {
135
- if (!options.force) {
136
- throw new Error(
137
- `Browser profile is already in use without a remote debugging port. Re-run with --force to safely close it first: ${detectedInstance.userDataDir}`,
138
- );
139
- }
140
-
141
- if (detectedInstance.pids.length === 0) {
142
- throw new Error(
143
- `Browser profile is in use but no running browser PID was found for safe shutdown: ${detectedInstance.userDataDir}`,
144
- );
145
- }
146
- }
147
-
148
- if (detectedInstance.inUse && detectedInstance.pids.length > 0) {
149
- debugLog('initBrowser', 'detected in-use profile, closing existing processes', {
150
- pids: detectedInstance.pids,
151
- killTimeoutMs,
152
- remoteDebuggingPort,
153
- });
154
- for (const pid of await closeProcessesByPid(detectedInstance.pids, killTimeoutMs)) {
155
- closedPidSet.add(pid);
156
- }
157
- await waitForPortToClose(remoteDebuggingPort, killTimeoutMs);
158
- } else if (detectedInstance.staleLock) {
159
- if (!options.force) {
160
- throw new Error(`Profile is locked by stale files: ${detectedInstance.lockFiles.join(', ')}`);
161
- }
162
-
163
- removeLockFiles(detectedInstance.lockFiles);
164
- }
165
-
166
- const launched = await launchBrowser(spec, executablePath, userDataDir, remoteDebuggingPort, startupUrl);
167
- debugLog('initBrowser', 'launched browser', { remoteDebuggingPort, pid: launched.pid ?? null, startupUrl });
168
-
169
- const wsUrl = await waitForCdpReady(remoteDebuggingPort, connectTimeoutMs);
170
- debugLog('initBrowser', 'waited for cdp ready', { remoteDebuggingPort, connectTimeoutMs, wsUrl });
171
- if (!wsUrl) {
172
- throw new Error(`CDP did not become available on port ${remoteDebuggingPort}`);
173
- }
174
-
175
- return {
176
- ok: true,
177
- type: spec.type,
178
- executablePath,
179
- userDataDir,
180
- remoteDebuggingPort,
181
- wsUrl,
182
- pid: launched.pid ?? await resolveBrowserPid(remoteDebuggingPort, detectedInstance.pid),
183
- };
184
- }
185
-
186
- export async function runConnectBrowserCommand(values: BrowserCliValues) {
187
- const resolved = await resolveConnectOptions({
188
- browser: values.browser,
189
- instanceName: values.instance,
190
- executablePath: values['executable-path'],
191
- userDataDir: values['user-data-dir'],
192
- force: values.force,
193
- remoteDebuggingPort: values.port ? Number(values.port) : undefined,
194
- connectTimeoutMs: values.timeout ? Number(values.timeout) : undefined,
195
- killTimeoutMs: values['kill-timeout'] ? Number(values['kill-timeout']) : undefined,
196
- startupUrl: values['startup-url'],
197
- });
198
- const result = await initBrowser(resolved.connectOptions);
199
-
200
- if (resolved.lastConnect) {
201
- updateLastConnect(resolved.lastConnect);
202
- }
203
-
204
- printJson(result);
205
- }
206
-
207
- async function main() {
208
- const { values } = parseBrowserCliArgs(process.argv.slice(2));
209
-
210
- if (values.help) {
211
- printConnectBrowserHelp();
212
- return;
213
- }
214
-
215
- await runConnectBrowserCommand(values);
216
- }
217
-
218
- runCli(import.meta.url, main);
@@ -1,81 +0,0 @@
1
- #!/usr/bin/env -S node --experimental-strip-types
2
-
3
- import {
4
- customInstanceUserDataDir,
5
- ensureDir,
6
- isDefaultInstanceName,
7
- readInstanceStore,
8
- type CreateInstanceOptions,
9
- type CreateInstanceResult,
10
- type PersistedInstance,
11
- validateInstanceName,
12
- writeInstanceStore,
13
- } from '../utils/browser-core.ts';
14
- import {
15
- parseBrowserCliArgs,
16
- printCreateBrowserHelp,
17
- printJson,
18
- runCli,
19
- type BrowserCliValues,
20
- } from '../utils/browser-cli.ts';
21
-
22
- export function createBrowserInstance(options: CreateInstanceOptions): CreateInstanceResult {
23
- const store = readInstanceStore();
24
- const name = validateInstanceName(options.name);
25
- const browser = options.browser ?? 'chrome';
26
-
27
- if (isDefaultInstanceName(name)) {
28
- throw new Error(`Instance name is reserved for a default browser: ${name}`);
29
- }
30
-
31
- if (store.instances.some((instance) => instance.name === name)) {
32
- throw new Error(`Instance already exists: ${name}`);
33
- }
34
-
35
- const instance: PersistedInstance = {
36
- name,
37
- browser,
38
- userDataDir: ensureDir(customInstanceUserDataDir(name)),
39
- createdAt: new Date().toISOString(),
40
- remoteDebuggingPort:
41
- typeof options.remoteDebuggingPort === 'number' && Number.isInteger(options.remoteDebuggingPort) && options.remoteDebuggingPort > 0
42
- ? options.remoteDebuggingPort
43
- : undefined,
44
- };
45
-
46
- writeInstanceStore({
47
- ...store,
48
- instances: [...store.instances, instance],
49
- });
50
-
51
- return {
52
- ok: true,
53
- instance,
54
- };
55
- }
56
-
57
- export function runCreateBrowserCommand(values: BrowserCliValues) {
58
- if (!values.name) {
59
- throw new Error('Missing required option: --name');
60
- }
61
-
62
- const result = createBrowserInstance({
63
- name: values.name,
64
- browser: values.browser,
65
- remoteDebuggingPort: values.port ? Number(values.port) : undefined,
66
- });
67
- printJson(result);
68
- }
69
-
70
- async function main() {
71
- const { values } = parseBrowserCliArgs(process.argv.slice(2));
72
-
73
- if (values.help) {
74
- printCreateBrowserHelp();
75
- return;
76
- }
77
-
78
- runCreateBrowserCommand(values);
79
- }
80
-
81
- runCli(import.meta.url, main);
@@ -1,49 +0,0 @@
1
- #!/usr/bin/env -S node --experimental-strip-types
2
-
3
- import {
4
- parseBrowserCliArgs,
5
- printInitBrowserHelp,
6
- runCli,
7
- } from '../utils/browser-cli.ts';
8
-
9
- export async function runBrowserCli(argv: string[] = process.argv.slice(2)) {
10
- const { values, positionals } = parseBrowserCliArgs(argv);
11
- const command = positionals[0] || 'list';
12
-
13
- if (values.help || command === 'help') {
14
- printInitBrowserHelp();
15
- return;
16
- }
17
-
18
- if (command === 'list') {
19
- const { runListBrowserCommand } = await import('./list-browser.ts');
20
- await runListBrowserCommand(values);
21
- return;
22
- }
23
-
24
- if (command === 'create') {
25
- const { runCreateBrowserCommand } = await import('./create-browser.ts');
26
- runCreateBrowserCommand(values);
27
- return;
28
- }
29
-
30
- if (command === 'remove') {
31
- const { runRemoveBrowserCommand } = await import('./remove-browser.ts');
32
- await runRemoveBrowserCommand(values);
33
- return;
34
- }
35
-
36
- if (command === 'connect') {
37
- const { runConnectBrowserCommand } = await import('./connect-browser.ts');
38
- await runConnectBrowserCommand(values);
39
- return;
40
- }
41
-
42
- throw new Error(`Unknown browser command: ${command}`);
43
- }
44
-
45
- async function main() {
46
- await runBrowserCli();
47
- }
48
-
49
- runCli(import.meta.url, main);
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env -S node --experimental-strip-types
2
-
3
- import {
4
- browserSpecs,
5
- findSpec,
6
- inspectBrowserInstance,
7
- isLastConnectMatch,
8
- readInstanceStore,
9
- toBrowserInstanceInfo,
10
- type ListBrowserInstancesResult,
11
- type ListedBrowserInstance,
12
- } from '../utils/browser-core.ts';
13
- import {
14
- parseBrowserCliArgs,
15
- printJson,
16
- printListBrowserHelp,
17
- runCli,
18
- type BrowserCliValues,
19
- } from '../utils/browser-cli.ts';
20
-
21
- export async function listBrowserInstances(): Promise<ListBrowserInstancesResult> {
22
- const specs = browserSpecs();
23
- const store = readInstanceStore();
24
- const defaultInstances = await Promise.all(
25
- specs.map(async (spec) => {
26
- const instance = await inspectBrowserInstance(spec);
27
- return {
28
- ...toBrowserInstanceInfo(instance),
29
- scope: 'default',
30
- instanceName: spec.type,
31
- createdAt: null,
32
- lastConnect: isLastConnectMatch(store.lastConnect, 'default', spec.type, spec.type),
33
- } satisfies ListedBrowserInstance;
34
- }),
35
- );
36
- const customInstances = await Promise.all(
37
- store.instances.map(async (persisted) => {
38
- const spec = findSpec(persisted.browser);
39
- const instance = await inspectBrowserInstance(spec, undefined, persisted.userDataDir);
40
- return {
41
- ...toBrowserInstanceInfo(instance),
42
- name: persisted.name,
43
- userDataDir: persisted.userDataDir,
44
- scope: 'custom',
45
- instanceName: persisted.name,
46
- createdAt: persisted.createdAt,
47
- lastConnect: isLastConnectMatch(store.lastConnect, 'custom', persisted.name, persisted.browser),
48
- } satisfies ListedBrowserInstance;
49
- }),
50
- );
51
-
52
- return {
53
- lastConnect: store.lastConnect,
54
- instances: [...defaultInstances, ...customInstances],
55
- };
56
- }
57
-
58
- export async function runListBrowserCommand(_values?: BrowserCliValues) {
59
- const instances = await listBrowserInstances();
60
- printJson(instances);
61
- }
62
-
63
- async function main() {
64
- const { values } = parseBrowserCliArgs(process.argv.slice(2));
65
-
66
- if (values.help) {
67
- printListBrowserHelp();
68
- return;
69
- }
70
-
71
- await runListBrowserCommand(values);
72
- }
73
-
74
- runCli(import.meta.url, main);
@@ -1,109 +0,0 @@
1
- #!/usr/bin/env -S node --experimental-strip-types
2
-
3
- import fs from 'node:fs';
4
- import {
5
- closeProcessesByPid,
6
- customInstanceUserDataDir,
7
- exists,
8
- findSpec,
9
- inspectBrowserInstance,
10
- INSTANCES_DIR,
11
- isDefaultInstanceName,
12
- isSubPath,
13
- normalizePath,
14
- readInstanceStore,
15
- type RemoveInstanceOptions,
16
- type RemoveInstanceResult,
17
- validateInstanceName,
18
- waitForPortToClose,
19
- writeInstanceStore,
20
- } from '../utils/browser-core.ts';
21
- import {
22
- parseBrowserCliArgs,
23
- printJson,
24
- printRemoveBrowserHelp,
25
- runCli,
26
- type BrowserCliValues,
27
- } from '../utils/browser-cli.ts';
28
-
29
- export async function removeBrowserInstance(options: RemoveInstanceOptions): Promise<RemoveInstanceResult> {
30
- const store = readInstanceStore();
31
- const name = validateInstanceName(options.name);
32
- if (isDefaultInstanceName(name)) {
33
- throw new Error(`Default instance cannot be removed: ${name}`);
34
- }
35
-
36
- const instance = store.instances.find((item) => item.name === name);
37
- if (!instance) {
38
- throw new Error(`Instance not found: ${name}`);
39
- }
40
-
41
- const expectedUserDataDir = customInstanceUserDataDir(name);
42
- const resolvedUserDataDir = normalizePath(instance.userDataDir);
43
- if (!isSubPath(INSTANCES_DIR, resolvedUserDataDir) || normalizePath(expectedUserDataDir) !== resolvedUserDataDir) {
44
- throw new Error(`Refusing to remove unexpected instance directory: ${instance.userDataDir}`);
45
- }
46
-
47
- const spec = findSpec(instance.browser);
48
- const detectedInstance = await inspectBrowserInstance(spec, undefined, resolvedUserDataDir);
49
- if (detectedInstance.inUse && !options.force) {
50
- throw new Error(`Instance is currently in use. Re-run with --force: ${name}`);
51
- }
52
-
53
- const closedPids: number[] = [];
54
- if (options.force && detectedInstance.pids.length > 0) {
55
- for (const pid of await closeProcessesByPid(detectedInstance.pids, 8_000)) {
56
- closedPids.push(pid);
57
- }
58
-
59
- if (detectedInstance.remotePort !== null) {
60
- await waitForPortToClose(detectedInstance.remotePort, 8_000);
61
- }
62
- }
63
-
64
- const removedDir = exists(resolvedUserDataDir);
65
- fs.rmSync(resolvedUserDataDir, { recursive: true, force: true });
66
-
67
- writeInstanceStore({
68
- ...store,
69
- lastConnect:
70
- store.lastConnect?.scope === 'custom' &&
71
- store.lastConnect.name === instance.name &&
72
- store.lastConnect.browser === instance.browser
73
- ? null
74
- : store.lastConnect,
75
- instances: store.instances.filter((item) => item.name !== instance.name),
76
- });
77
-
78
- return {
79
- ok: true,
80
- removedInstance: instance,
81
- removedDir,
82
- closedPids,
83
- };
84
- }
85
-
86
- export async function runRemoveBrowserCommand(values: BrowserCliValues) {
87
- if (!values.name) {
88
- throw new Error('Missing required option: --name');
89
- }
90
-
91
- const result = await removeBrowserInstance({
92
- name: values.name,
93
- force: values.force,
94
- });
95
- printJson(result);
96
- }
97
-
98
- async function main() {
99
- const { values } = parseBrowserCliArgs(process.argv.slice(2));
100
-
101
- if (values.help) {
102
- 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`);
103
- return;
104
- }
105
-
106
- await runRemoveBrowserCommand(values);
107
- }
108
-
109
- runCli(import.meta.url, main);