byr-pt-cli 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 onemoreproduct
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,64 +1,85 @@
1
1
  # byr-pt-cli
2
2
 
3
- `byr-cli` is a standalone CLI package with OpenClaw-friendly JSON output contracts.
3
+ CLI for `byr.pt` with stable machine-readable output (`--json`) and script-friendly commands for search, details, download, auth, and user stats.
4
4
 
5
- ## Commands
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i -g byr-pt-cli
9
+ byr --help
10
+ ```
11
+
12
+ ## Quick start
6
13
 
7
14
  ```bash
15
+ # Use cookie (recommended)
8
16
  export BYR_COOKIE='uid=...; pass=...'
9
- # or: export BYR_USERNAME='...' BYR_PASSWORD='...'
10
17
 
18
+ # Or username/password
19
+ # export BYR_USERNAME='...'
20
+ # export BYR_PASSWORD='...'
21
+
22
+ byr search --query "ubuntu" --limit 5
23
+ byr get --id 1001
24
+ byr download --id 1001 --output ./1001.torrent --dry-run
25
+ ```
26
+
27
+ ## Commands
28
+
29
+ ```bash
11
30
  byr search --query "ubuntu" --limit 5 --category movie --spstate free
12
31
  byr search --imdb tt0133093 --json
13
32
  byr get --id 1001
14
- byr download --id 1001 --output ./1001.torrent --dry-run
33
+ byr download --id 1001 --output ./1001.torrent
15
34
  byr user info --json
16
35
  byr meta categories --json
17
36
  byr meta levels --json
18
37
  byr auth status --verify --json
38
+ byr auth import-cookie --cookie "uid=...; pass=..." --json
19
39
  byr auth import-cookie --from-browser chrome --profile "Default" --json
20
40
  byr auth logout --json
21
41
  ```
22
42
 
23
- `auth import-cookie` accepts both cookie formats:
43
+ ## Search filters
24
44
 
25
- - legacy: `uid=...; pass=...`
26
- - current BYR session: `session_id=...; auth_token=...` (optional `refresh_token=...`)
45
+ - `--category` repeatable / comma-separated, accepts alias or numeric ID
46
+ - `--incldead` `all|alive|dead` (or `0|1|2`)
47
+ - `--spstate` `all|normal|free|2x|2xfree|50|2x50|30` (or `0..7`)
48
+ - `--bookmarked` `all|only|unbookmarked` (or `0|1|2`)
49
+ - `--imdb` alternative to `--query`
50
+ - `--page` page index
27
51
 
28
- Search flags (new):
52
+ ## Auth and config
29
53
 
30
- - `--category` (repeatable and comma-separated, accepts aliases/id)
31
- - `--incldead` (`all|alive|dead` or `0|1|2`)
32
- - `--spstate` (`all|normal|free|2x|2xfree|50|2x50|30` or `0..7`)
33
- - `--bookmarked` (`all|only|unbookmarked` or `0|1|2`)
34
- - `--imdb` (alternative to `--query`)
35
- - `--page`
54
+ Credential source priority:
36
55
 
37
- Auth/config priority:
56
+ `CLI flags > ENV > ./.byrrc.json > ~/.config/byr-cli/config.json > ~/.config/byr-cli/auth.json`
38
57
 
39
- - `CLI flags > ENV > ./.byrrc.json > ~/.config/byr-cli/config.json > ~/.config/byr-cli/auth.json`
58
+ `auth import-cookie` supports:
59
+
60
+ - legacy BYR cookie: `uid=...; pass=...`
61
+ - current session cookie: `session_id=...; auth_token=...` (optional `refresh_token=...`)
40
62
 
41
63
  ## JSON contract
42
64
 
43
- Every command supports `--json`.
65
+ All commands support `--json`.
44
66
 
45
67
  - success: `{ "ok": true, "data": ..., "meta": ... }`
46
68
  - error: `{ "ok": false, "error": { "code": "...", "message": "...", "details": ... } }`
47
69
 
48
- ## Local live smoke
70
+ ## Testing
71
+
72
+ Run package tests and checks:
49
73
 
50
74
  ```bash
51
- BYR_LIVE=1 BYR_COOKIE='uid=...; pass=...' pnpm --filter byr-pt-cli test:live
75
+ pnpm --filter byr-pt-cli test
76
+ pnpm --filter byr-pt-cli check
52
77
  ```
53
78
 
54
- Live smoke flow: `search -> get -> download --dry-run -> user info`.
55
- CI should keep `test:live` disabled and only run fixture/mock tests.
56
-
57
- ## Skill bundle
58
-
59
- OpenClaw runtime skill is in `skill-openclaw/` and can be published to ClawHub.
79
+ Local live smoke (requires valid BYR credentials):
60
80
 
61
81
  ```bash
62
- # Reads publish settings from skill-openclaw/publish.json
63
- pnpm --filter byr-pt-cli skill:publish
82
+ BYR_LIVE=1 BYR_COOKIE='uid=...; pass=...' pnpm --filter byr-pt-cli test:live
64
83
  ```
84
+
85
+ Flow: `search -> get -> download --dry-run -> user info`.
@@ -2,9 +2,9 @@ import { renderDownloadOutput, runDownloadCommand } from "./commands/download.mj
2
2
  import { renderGetOutput, runGetCommand } from "./commands/get.mjs";
3
3
  import { renderSearchOutput, runSearchCommand } from "./commands/search.mjs";
4
4
  import { a as BYR_INCLDEAD_FACET, c as parseCategoryAliases, i as BYR_BOOKMARKED_FACET, l as parseSimpleFacetAliases, o as BYR_SPSTATE_FACET, s as getByrMetadata, t as createByrClient } from "./client-DT9oDCaE.mjs";
5
- import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
5
+ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
6
6
  import { CliAppError, EXIT_CODES, createCommandContext, createErrorEnvelope, createSuccessEnvelope, mapErrorCodeToExitCode, toCliAppError } from "@onemoreproduct/cli-core";
7
- import { copyFileSync, existsSync, readFileSync, statSync } from "node:fs";
7
+ import { copyFileSync, existsSync, readFileSync, rmSync, statSync } from "node:fs";
8
8
  import { homedir, tmpdir } from "node:os";
9
9
  import { dirname, join } from "node:path";
10
10
  import { createDecipheriv, pbkdf2Sync, randomUUID } from "node:crypto";
@@ -17,6 +17,13 @@ const TARGET_DOMAINS = new Set([
17
17
  ".bt.byr.cn",
18
18
  "bt.byr.cn"
19
19
  ]);
20
+ const AUTH_COOKIE_NAMES = new Set([
21
+ "uid",
22
+ "pass",
23
+ "session_id",
24
+ "auth_token",
25
+ "refresh_token"
26
+ ]);
20
27
  async function importCookieFromBrowser(browser, profile) {
21
28
  if (process.platform !== "darwin") throw new CliAppError({
22
29
  code: "E_AUTH_REQUIRED",
@@ -44,43 +51,46 @@ function importCookieFromChrome(profile) {
44
51
  const shm = `${dbPath}-shm`;
45
52
  if (existsSync(wal)) copyFileSync(wal, `${tempDb}-wal`);
46
53
  if (existsSync(shm)) copyFileSync(shm, `${tempDb}-shm`);
47
- const sqlite = spawnSync("sqlite3", [
48
- "-separator",
49
- " ",
50
- tempDb,
51
- "SELECT name, value, hex(encrypted_value) FROM cookies WHERE host_key IN ('.byr.pt','byr.pt','.bt.byr.cn','bt.byr.cn') AND name IN ('uid','pass')"
52
- ], { encoding: "utf8" });
53
- if (sqlite.status !== 0) throw new CliAppError({
54
- code: "E_AUTH_REQUIRED",
55
- message: "Failed to read Chrome cookies with sqlite3",
56
- details: { stderr: sqlite.stderr?.trim() }
57
- });
58
- const decrypted = /* @__PURE__ */ new Map();
59
- const lines = (sqlite.stdout ?? "").split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
60
- let keyHex;
61
- for (const line of lines) {
62
- const [name, value, encryptedHex] = line.split(" ");
63
- if (name !== "uid" && name !== "pass") continue;
64
- if (value && value.length > 0) {
65
- decrypted.set(name, value);
66
- continue;
54
+ try {
55
+ const sqlite = spawnSync("sqlite3", [
56
+ "-separator",
57
+ " ",
58
+ tempDb,
59
+ "SELECT name, value, hex(encrypted_value) FROM cookies WHERE host_key IN ('.byr.pt','byr.pt','.bt.byr.cn','bt.byr.cn') AND name IN ('uid','pass','session_id','auth_token','refresh_token')"
60
+ ], { encoding: "utf8" });
61
+ if (sqlite.status !== 0) throw new CliAppError({
62
+ code: "E_AUTH_REQUIRED",
63
+ message: "Failed to read Chrome cookies with sqlite3",
64
+ details: { stderr: sqlite.stderr?.trim() }
65
+ });
66
+ const cookies = /* @__PURE__ */ new Map();
67
+ const lines = (sqlite.stdout ?? "").split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
68
+ let keyHex;
69
+ for (const line of lines) {
70
+ const [name, value, encryptedHex] = line.split(" ");
71
+ if (!AUTH_COOKIE_NAMES.has(name)) continue;
72
+ if (value && value.length > 0) {
73
+ cookies.set(name, value);
74
+ continue;
75
+ }
76
+ if (!keyHex) keyHex = getChromeSafeStorageKeyHex();
77
+ if (!encryptedHex || encryptedHex.length === 0 || !keyHex) continue;
78
+ const resolved = decryptChromeCookieHex(encryptedHex, keyHex);
79
+ if (resolved) cookies.set(name, resolved);
67
80
  }
68
- if (!keyHex) keyHex = getChromeSafeStorageKeyHex();
69
- if (!encryptedHex || encryptedHex.length === 0 || !keyHex) continue;
70
- const resolved = decryptChromeCookieHex(encryptedHex, keyHex);
71
- if (resolved) decrypted.set(name, resolved);
81
+ return {
82
+ cookie: buildByrAuthCookieHeader(cookies, {
83
+ source: `chrome:${profileName}`,
84
+ profile: profileName
85
+ }),
86
+ source: `chrome:${profileName}`
87
+ };
88
+ } finally {
89
+ rmSync(tempDir, {
90
+ recursive: true,
91
+ force: true
92
+ });
72
93
  }
73
- const uid = decrypted.get("uid");
74
- const pass = decrypted.get("pass");
75
- if (!uid || !pass) throw new CliAppError({
76
- code: "E_AUTH_REQUIRED",
77
- message: "Unable to extract uid/pass from Chrome cookies",
78
- details: { profile: profileName }
79
- });
80
- return {
81
- cookie: `uid=${uid}; pass=${pass}`,
82
- source: `chrome:${profileName}`
83
- };
84
94
  }
85
95
  function getChromeSafeStorageKeyHex() {
86
96
  for (const [service, account] of [["Chrome Safe Storage", "Chrome"], ["Chrome Safe Storage", "Google Chrome"]]) {
@@ -137,7 +147,7 @@ function tryReadSafariSqlite(path) {
137
147
  "-separator",
138
148
  " ",
139
149
  path,
140
- "SELECT name, value, host FROM cookies WHERE host IN ('.byr.pt','byr.pt','.bt.byr.cn','bt.byr.cn') AND name IN ('uid','pass')"
150
+ "SELECT name, value, host FROM cookies WHERE host IN ('.byr.pt','byr.pt','.bt.byr.cn','bt.byr.cn') AND name IN ('uid','pass','session_id','auth_token','refresh_token')"
141
151
  ], { encoding: "utf8" });
142
152
  if (sqlite.status !== 0) return;
143
153
  const records = [];
@@ -168,15 +178,37 @@ function extractByrCookie(records, source) {
168
178
  const map = /* @__PURE__ */ new Map();
169
179
  for (const record of records) {
170
180
  if (!TARGET_DOMAINS.has(record.domain)) continue;
171
- if (record.name === "uid" || record.name === "pass") map.set(record.name, record.value);
181
+ if (AUTH_COOKIE_NAMES.has(record.name)) map.set(record.name, record.value);
172
182
  }
173
- const uid = map.get("uid");
174
- const pass = map.get("pass");
175
- if (!uid || !pass) return;
176
- return {
177
- cookie: `uid=${uid}; pass=${pass}`,
178
- source
179
- };
183
+ if (map.size === 0) return;
184
+ try {
185
+ return {
186
+ cookie: buildByrAuthCookieHeader(map, { source }),
187
+ source
188
+ };
189
+ } catch {
190
+ return;
191
+ }
192
+ }
193
+ function buildByrAuthCookieHeader(cookieMap, context) {
194
+ const uid = cookieMap.get("uid");
195
+ const pass = cookieMap.get("pass");
196
+ if (uid && pass) return `uid=${uid}; pass=${pass}`;
197
+ const sessionId = cookieMap.get("session_id");
198
+ const authToken = cookieMap.get("auth_token");
199
+ if (sessionId && authToken) {
200
+ const refreshToken = cookieMap.get("refresh_token");
201
+ return refreshToken ? `session_id=${sessionId}; auth_token=${authToken}; refresh_token=${refreshToken}` : `session_id=${sessionId}; auth_token=${authToken}`;
202
+ }
203
+ throw new CliAppError({
204
+ code: "E_AUTH_REQUIRED",
205
+ message: "Unable to extract BYR auth cookies from browser storage",
206
+ details: {
207
+ source: context.source,
208
+ profile: context.profile,
209
+ requiredAnyOf: [["uid", "pass"], ["session_id", "auth_token"]]
210
+ }
211
+ });
180
212
  }
181
213
  function parseBinaryCookies(buffer) {
182
214
  if (buffer.length < 8 || buffer.subarray(0, 4).toString("ascii") !== "cook") return [];
@@ -290,9 +322,23 @@ async function writeAuthStore(cookie, source) {
290
322
  return store;
291
323
  }
292
324
  async function clearAuthStore() {
325
+ const authStorePath = getByrAuthStorePath();
326
+ let existed = true;
327
+ try {
328
+ await stat(authStorePath);
329
+ } catch (error) {
330
+ const nodeError = error;
331
+ if (nodeError.code === "ENOENT") existed = false;
332
+ else throw new CliAppError({
333
+ code: "E_AUTH_INVALID",
334
+ message: "Failed to inspect BYR auth store",
335
+ details: { reason: nodeError.message },
336
+ cause: error
337
+ });
338
+ }
293
339
  try {
294
- await rm(getByrAuthStorePath(), { force: true });
295
- return true;
340
+ await rm(authStorePath, { force: true });
341
+ return existed;
296
342
  } catch (error) {
297
343
  throw new CliAppError({
298
344
  code: "E_AUTH_INVALID",
package/dist/cli.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  import "./client-DT9oDCaE.mjs";
2
- import { t as runCli } from "./cli-CdpxYIyd.mjs";
2
+ import { t as runCli } from "./cli-B6Pr98en.mjs";
3
3
 
4
4
  export { runCli };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import "./client-DT9oDCaE.mjs";
3
- import { t as runCli } from "./cli-CdpxYIyd.mjs";
3
+ import { t as runCli } from "./cli-B6Pr98en.mjs";
4
4
 
5
5
  //#region src/index.ts
6
6
  const exitCode = await runCli(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "byr-pt-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "description": "BYR CLI with OpenClaw-friendly JSON contract",
6
6
  "license": "MIT",
@@ -20,16 +20,16 @@
20
20
  "import": "./dist/cli.mjs"
21
21
  }
22
22
  },
23
+ "dependencies": {
24
+ "clawkit-cli-core": "^0.1.0"
25
+ },
23
26
  "scripts": {
24
27
  "build": "tsdown src/index.ts src/cli.ts src/domain/client.ts src/domain/types.ts src/commands/download.ts src/commands/get.ts src/commands/search.ts --format esm --dts",
25
- "test": "pnpm --filter @onemoreproduct/cli-core build && vitest run",
28
+ "test": "pnpm --filter clawkit-cli-core build && vitest run",
26
29
  "test:live": "tsx ./scripts/test-live.ts",
27
30
  "typecheck": "tsc --project tsconfig.json --noEmit",
28
31
  "check": "pnpm typecheck && pnpm test",
29
32
  "skill:publish": "node ./scripts/skill-publish.mjs",
30
33
  "skill:sync": "clawhub sync --root ./skill-openclaw --all"
31
- },
32
- "dependencies": {
33
- "@onemoreproduct/cli-core": "workspace:*"
34
34
  }
35
- }
35
+ }