frigatebird 0.2.0
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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/SKILL.md +51 -0
- package/SPEC.md +59 -0
- package/TASKS.md +29 -0
- package/dist/browser/auth-store.js +145 -0
- package/dist/browser/scrape.js +247 -0
- package/dist/browser/session-manager.js +102 -0
- package/dist/cli/program.js +299 -0
- package/dist/cli.js +112 -0
- package/dist/client/client.js +1 -0
- package/dist/client/playwright-client.js +1058 -0
- package/dist/commands/handlers.js +295 -0
- package/dist/lib/config.js +75 -0
- package/dist/lib/identifiers.js +60 -0
- package/dist/lib/invocation.js +49 -0
- package/dist/lib/options.js +169 -0
- package/dist/lib/output.js +171 -0
- package/dist/lib/types.js +1 -0
- package/package.json +66 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 - 2026-02-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Modular architecture split across `cli`, `commands`, `client`, `browser`, and `lib` domains.
|
|
7
|
+
- Expanded bird-compatible CLI surface including timeline/search/news/lists/follows/likes/bookmarks/about/query commands.
|
|
8
|
+
- Integrated `x-list-manager` workflows: `refresh`, `add`, `remove`, and `batch`.
|
|
9
|
+
- Config and environment precedence matching bird semantics.
|
|
10
|
+
- Media attachment validation parity (`--media`, `--alt`, max counts, video constraints).
|
|
11
|
+
- Deterministic read-only end-to-end tests using local fixture pages and `--base-url`.
|
|
12
|
+
- Project skill files: `SKILL.md`, `SPEC.md`, `TASKS.md`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Runtime engine standardized on Playwright browser automation instead of GraphQL internals.
|
|
16
|
+
- `query-ids` retained as a compatibility command in Playwright mode.
|
|
17
|
+
- Documentation now explicitly describes Frigatebird intent and contrast with bird.
|
|
18
|
+
|
|
19
|
+
### Tests
|
|
20
|
+
- Added broad unit coverage across option parsing, invocation normalization, handlers, and Playwright client behavior.
|
|
21
|
+
- Added read-only empty-account e2e coverage for command stability in low-data scenarios.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sean McLellan
|
|
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
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frigatebird
|
|
2
|
+
|
|
3
|
+
`frigatebird` is a Playwright-first X CLI that targets `bird` command parity and pulls in `x-list-manager` list automation.
|
|
4
|
+
|
|
5
|
+
It keeps the `bird` UX, but replaces deprecated/non-open GraphQL internals with browser automation against X web UI.
|
|
6
|
+
|
|
7
|
+
## Intent
|
|
8
|
+
|
|
9
|
+
Frigatebird exists to keep the `bird` workflow alive after deprecation and closure:
|
|
10
|
+
|
|
11
|
+
- Preserve familiar command ergonomics from `bird`.
|
|
12
|
+
- Keep zero-API-key operation via local browser session cookies.
|
|
13
|
+
- Add first-class list management from `x-list-manager` (`add`, `remove`, `batch`, `refresh`).
|
|
14
|
+
- Provide one CLI for read workflows, account actions, and list operations.
|
|
15
|
+
|
|
16
|
+
## Frigatebird vs Bird
|
|
17
|
+
|
|
18
|
+
| Area | `bird` | `frigatebird` |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Primary engine | X internal GraphQL + query IDs | Playwright web automation |
|
|
21
|
+
| API keys | Not required | Not required |
|
|
22
|
+
| Auth | Browser cookies / env tokens | Browser cookies / env tokens |
|
|
23
|
+
| `query-ids` | Active GraphQL cache/refresh behavior | Compatibility command in Playwright mode |
|
|
24
|
+
| List manager commands | External project (`x-list-manager`) | Built in (`refresh`, `add`, `remove`, `batch`) |
|
|
25
|
+
| Selector/query fragility | Query ID churn | DOM selector churn |
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install
|
|
31
|
+
npx playwright install chromium
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# One-off
|
|
38
|
+
npx tsx src/cli.ts whoami
|
|
39
|
+
|
|
40
|
+
# Built binary
|
|
41
|
+
npm run build
|
|
42
|
+
node dist/cli.js --help
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
### bird-style parity commands
|
|
48
|
+
|
|
49
|
+
- `tweet <text>`
|
|
50
|
+
- `post <text>`
|
|
51
|
+
- `reply <tweet-id-or-url> <text>`
|
|
52
|
+
- `read <tweet-id-or-url> [--json] [--json-full]`
|
|
53
|
+
- `replies <tweet-id-or-url> [-n count] [--all] [--max-pages n] [--cursor str] [--delay ms] [--json] [--json-full]`
|
|
54
|
+
- `thread <tweet-id-or-url> [-n count] [--all] [--max-pages n] [--cursor str] [--delay ms] [--json] [--json-full]`
|
|
55
|
+
- `search <query> [-n count] [--all] [--max-pages n] [--cursor str] [--delay ms] [--json] [--json-full]`
|
|
56
|
+
- `mentions [-u @handle] [-n count] [--json] [--json-full]`
|
|
57
|
+
- `user-tweets <@handle> [-n count] [--all] [--max-pages n] [--cursor str] [--delay ms] [--json] [--json-full]`
|
|
58
|
+
- `home [-n count] [--following] [--all] [--max-pages n] [--delay ms] [--json] [--json-full]`
|
|
59
|
+
- `bookmarks [-n count] [--folder-id id] [--all] [--max-pages n] [--cursor str] [--expand-root-only] [--author-chain] [--author-only] [--full-chain-only] [--include-ancestor-branches] [--include-parent] [--thread-meta] [--sort-chronological] [--delay ms] [--json] [--json-full]`
|
|
60
|
+
- `unbookmark <tweet-id-or-url...> [--json]`
|
|
61
|
+
- `like <tweet-id-or-url>`
|
|
62
|
+
- `retweet <tweet-id-or-url>`
|
|
63
|
+
- `likes [-n count] [--all] [--max-pages n] [--cursor str] [--delay ms] [--json] [--json-full]`
|
|
64
|
+
- `follow <username-or-id>`
|
|
65
|
+
- `unfollow <username-or-id>`
|
|
66
|
+
- `following [--user userId] [-n count] [--all] [--max-pages n] [--cursor str] [--delay ms] [--json] [--json-full]`
|
|
67
|
+
- `followers [--user userId] [-n count] [--all] [--max-pages n] [--cursor str] [--delay ms] [--json] [--json-full]`
|
|
68
|
+
- `lists [--member-of] [-n count] [--json] [--json-full]`
|
|
69
|
+
- `list` (alias for `lists`)
|
|
70
|
+
- `list-timeline <list-id-or-url> [-n count] [--all] [--max-pages n] [--cursor str] [--delay ms] [--json] [--json-full]`
|
|
71
|
+
- `news [-n count] [--ai-only] [--with-tweets] [--tweets-per-item n] [--for-you] [--news-only] [--sports] [--entertainment] [--trending-only] [--json] [--json-full]`
|
|
72
|
+
- `trending` (alias for `news`)
|
|
73
|
+
- `about <@handle> [--json]`
|
|
74
|
+
- `query-ids [--fresh] [--json]` (compatibility mode in Playwright engine)
|
|
75
|
+
- `whoami [--json]`
|
|
76
|
+
- `check`
|
|
77
|
+
- `help [command]`
|
|
78
|
+
|
|
79
|
+
### x-list-manager parity commands
|
|
80
|
+
|
|
81
|
+
- `refresh [--json]`
|
|
82
|
+
- `add <listName> <handles...> [--no-headless] [--json]`
|
|
83
|
+
- `remove <handle> <listName> [--no-headless] [--json]`
|
|
84
|
+
- `batch <file.json> [--no-headless] [--json]`
|
|
85
|
+
|
|
86
|
+
## Global Options
|
|
87
|
+
|
|
88
|
+
- `--auth-token <token>`
|
|
89
|
+
- `--ct0 <token>`
|
|
90
|
+
- `--base-url <url>` (testing override, default `https://x.com`)
|
|
91
|
+
- `--cookie-source <chrome|firefox|safari|edge>` (repeatable)
|
|
92
|
+
- `--chrome-profile <name>`
|
|
93
|
+
- `--chrome-profile-dir <path>`
|
|
94
|
+
- `--firefox-profile <name>`
|
|
95
|
+
- `--cookie-timeout <ms>`
|
|
96
|
+
- `--timeout <ms>`
|
|
97
|
+
- `--quote-depth <n>`
|
|
98
|
+
- `--media <path>` (repeatable)
|
|
99
|
+
- `--alt <text>` (repeatable)
|
|
100
|
+
- `--plain`
|
|
101
|
+
- `--no-emoji`
|
|
102
|
+
- `--no-color`
|
|
103
|
+
- `--no-headless`
|
|
104
|
+
|
|
105
|
+
`tweet` and `reply` both consume `--media`/`--alt`.
|
|
106
|
+
|
|
107
|
+
Media constraints:
|
|
108
|
+
- up to 4 attachments
|
|
109
|
+
- only one video
|
|
110
|
+
- video cannot be mixed with other media
|
|
111
|
+
- supported extensions: `jpg`, `jpeg`, `png`, `webp`, `gif`, `mp4`, `m4v`, `mov`
|
|
112
|
+
|
|
113
|
+
## Config + Env
|
|
114
|
+
|
|
115
|
+
Precedence: **CLI flags > env vars > project config > global config**.
|
|
116
|
+
|
|
117
|
+
Config files:
|
|
118
|
+
- Global (bird): `~/.config/bird/config.json5`
|
|
119
|
+
- Global (frigatebird): `~/.config/frigatebird/config.json5`
|
|
120
|
+
- Project (bird): `./.birdrc.json5`
|
|
121
|
+
- Project (frigatebird): `./.frigatebirdrc.json5`
|
|
122
|
+
|
|
123
|
+
Supported config keys:
|
|
124
|
+
- `authToken`, `ct0`, `baseUrl`
|
|
125
|
+
- `cookieSource`
|
|
126
|
+
- `chromeProfile`, `chromeProfileDir`, `firefoxProfile`
|
|
127
|
+
- `cookieTimeoutMs`, `timeoutMs`, `quoteDepth`
|
|
128
|
+
|
|
129
|
+
Supported env vars:
|
|
130
|
+
- Auth: `AUTH_TOKEN`, `CT0`, `TWITTER_AUTH_TOKEN`, `TWITTER_CT0`
|
|
131
|
+
- Cookie source: `BIRD_COOKIE_SOURCE`, `FRIGATEBIRD_COOKIE_SOURCE`
|
|
132
|
+
- Base URL: `BIRD_BASE_URL`, `FRIGATEBIRD_BASE_URL`
|
|
133
|
+
- Profiles: `BIRD_CHROME_PROFILE`, `BIRD_CHROME_PROFILE_DIR`, `BIRD_FIREFOX_PROFILE` (plus `FRIGATEBIRD_*` variants)
|
|
134
|
+
- Timeouts/depth: `BIRD_TIMEOUT_MS`, `BIRD_COOKIE_TIMEOUT_MS`, `BIRD_QUOTE_DEPTH` (plus `FRIGATEBIRD_*` variants)
|
|
135
|
+
- Output: `NO_COLOR`, `BIRD_PLAIN`, `FRIGATEBIRD_PLAIN`
|
|
136
|
+
|
|
137
|
+
## Shorthand Invocation
|
|
138
|
+
|
|
139
|
+
If first argument is a tweet URL or ID, Frigatebird rewrites to `read`:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx tsx src/cli.ts 1891234567890123456 --json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Architecture
|
|
146
|
+
|
|
147
|
+
- `src/cli/`: commander program assembly and global option wiring
|
|
148
|
+
- `src/commands/`: handler layer and output orchestration
|
|
149
|
+
- `src/client/`: `FrigatebirdClient` interface and Playwright implementation
|
|
150
|
+
- `src/browser/`: auth store, session lifecycle, scrape primitives
|
|
151
|
+
- `src/lib/`: identifiers, option parsing, invocation normalization, config, output
|
|
152
|
+
|
|
153
|
+
## Skills Files
|
|
154
|
+
|
|
155
|
+
Frigatebird now includes skill-oriented project files (same ecosystem style used by `x-list-manager`):
|
|
156
|
+
|
|
157
|
+
- `SKILL.md`: AI-agent usage contract for this CLI
|
|
158
|
+
- `SPEC.md`: technical architecture and command behavior
|
|
159
|
+
- `TASKS.md`: parity/release checklist
|
|
160
|
+
|
|
161
|
+
## Release Readiness
|
|
162
|
+
|
|
163
|
+
- `CHANGELOG.md` tracks release notes.
|
|
164
|
+
- `RELEASE.md` contains the release checklist and publish flow.
|
|
165
|
+
- GitHub workflows enforce CI + publish gates:
|
|
166
|
+
- `.github/workflows/ci.yml`
|
|
167
|
+
- `.github/workflows/release.yml`
|
|
168
|
+
- npm publish is triggered by GitHub Release `published` events using npm trusted publishing (OIDC, no npm token secret).
|
|
169
|
+
- `npm run release:check` runs lint + build + full tests + coverage.
|
|
170
|
+
- `npm run smoke:pack-install` validates clean install + binary entrypoint from a packed tarball.
|
|
171
|
+
|
|
172
|
+
## Test
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm run test:run
|
|
176
|
+
npm run test:coverage
|
|
177
|
+
npm run test:e2e
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`test:run` and `test:coverage` run unit/integration suites only.
|
|
181
|
+
`test:e2e` runs end-to-end read-only tests against empty-account fixtures.
|
|
182
|
+
|
|
183
|
+
## Notes
|
|
184
|
+
|
|
185
|
+
- Frigatebird automates X web UI and depends on selectors that may change.
|
|
186
|
+
- Some deep `bird` GraphQL-only semantics are represented as compatibility flags in Playwright mode.
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frigatebird
|
|
3
|
+
description: Use Frigatebird to interact with X from the CLI with bird-compatible commands plus x-list-manager list automation. Use when users ask to read timelines/tweets, manage follows/lists, or automate list membership without X API keys.
|
|
4
|
+
argument-hint: 'whoami, read https://x.com/user/status/123, search "from:openai", add "AI News" @openai @anthropicai'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Frigatebird Skill
|
|
8
|
+
|
|
9
|
+
Frigatebird is a Playwright-first CLI that preserves `bird` command ergonomics and includes `x-list-manager` list operations.
|
|
10
|
+
|
|
11
|
+
## Use This Skill When
|
|
12
|
+
|
|
13
|
+
- The user asks for `bird`-style CLI interactions on X.
|
|
14
|
+
- The user wants list automation (`add`, `remove`, `batch`, `refresh`).
|
|
15
|
+
- The user wants browser-cookie-based operation without API keys.
|
|
16
|
+
|
|
17
|
+
## Core Workflow
|
|
18
|
+
|
|
19
|
+
1. Verify auth/session health:
|
|
20
|
+
- `npx tsx src/cli.ts check`
|
|
21
|
+
- `npx tsx src/cli.ts whoami`
|
|
22
|
+
2. For read-only tasks, prefer JSON output:
|
|
23
|
+
- `npx tsx src/cli.ts read <tweet-id-or-url> --json`
|
|
24
|
+
- `npx tsx src/cli.ts search "<query>" --json`
|
|
25
|
+
3. For list-management tasks:
|
|
26
|
+
- `npx tsx src/cli.ts add "<List Name>" @handle1 @handle2`
|
|
27
|
+
- `npx tsx src/cli.ts remove @handle "<List Name>"`
|
|
28
|
+
- `npx tsx src/cli.ts batch accounts.json`
|
|
29
|
+
4. Use pagination controls for large reads:
|
|
30
|
+
- `--all`, `--max-pages`, `--cursor`, `-n`
|
|
31
|
+
|
|
32
|
+
## Command Groups
|
|
33
|
+
|
|
34
|
+
- Posting/mutations: `tweet`, `post`, `reply`, `like`, `retweet`, `follow`, `unfollow`, `unbookmark`
|
|
35
|
+
- Read/timelines: `read`, `replies`, `thread`, `search`, `mentions`, `user-tweets`, `home`, `bookmarks`, `likes`, `list-timeline`, `news`, `about`
|
|
36
|
+
- Identity/health: `check`, `whoami`, `query-ids`, `help`
|
|
37
|
+
- List automation: `refresh`, `add`, `remove`, `batch`, `lists`, `list`
|
|
38
|
+
|
|
39
|
+
## Options That Matter Most
|
|
40
|
+
|
|
41
|
+
- Auth/cookies: `--auth-token`, `--ct0`, `--cookie-source`, `--chrome-profile`, `--firefox-profile`
|
|
42
|
+
- Determinism/testing: `--base-url`, `--plain`, `--no-color`
|
|
43
|
+
- Pagination: `-n`, `--all`, `--max-pages`, `--cursor`, `--delay`
|
|
44
|
+
- Output: `--json`, `--json-full`
|
|
45
|
+
- Media posting: `--media`, `--alt`
|
|
46
|
+
|
|
47
|
+
## Caveats
|
|
48
|
+
|
|
49
|
+
- This tool depends on X web UI selectors; selector drift can break flows.
|
|
50
|
+
- `query-ids` is retained for command compatibility and does not drive Playwright execution.
|
|
51
|
+
- Some GraphQL-specific behavior from original `bird` is represented as compatibility flags in Playwright mode.
|
package/SPEC.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Frigatebird Technical Specification
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Frigatebird is a TypeScript CLI for interacting with X via browser automation. It targets command-level parity with `bird` and includes list-management capabilities from `x-list-manager`.
|
|
6
|
+
|
|
7
|
+
## Product Intent
|
|
8
|
+
|
|
9
|
+
- Preserve the user-facing `bird` command model after `bird` deprecation.
|
|
10
|
+
- Replace unstable/private GraphQL dependency with Playwright-driven web interactions.
|
|
11
|
+
- Consolidate read, mutation, and list-management operations into one CLI.
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
### 1. CLI Assembly (`src/cli/`, `src/cli.ts`)
|
|
16
|
+
- Declares the command surface with Commander.
|
|
17
|
+
- Normalizes shorthand invocation (`<tweet-id-or-url>` -> `read`).
|
|
18
|
+
- Resolves option precedence: CLI > env > project config > global config.
|
|
19
|
+
|
|
20
|
+
### 2. Command Handlers (`src/commands/handlers.ts`)
|
|
21
|
+
- Maps CLI actions to client operations.
|
|
22
|
+
- Applies option parsing per command category.
|
|
23
|
+
- Normalizes JSON/plain output behavior.
|
|
24
|
+
|
|
25
|
+
### 3. Client Layer (`src/client/`)
|
|
26
|
+
- `FrigatebirdClient` interface defines all command capabilities.
|
|
27
|
+
- `PlaywrightXClient` implements behavior through X web navigation and DOM scraping.
|
|
28
|
+
|
|
29
|
+
### 4. Browser Layer (`src/browser/`)
|
|
30
|
+
- Session lifecycle and login checks.
|
|
31
|
+
- Cookie loading and auth store management.
|
|
32
|
+
- Scraping collectors for tweets, users, lists, and news.
|
|
33
|
+
|
|
34
|
+
### 5. Shared Library (`src/lib/`)
|
|
35
|
+
- Identifier parsing (`tweet`, `list`, `profile` references).
|
|
36
|
+
- Option parsing and normalization.
|
|
37
|
+
- Output formatting and rendering.
|
|
38
|
+
- Config/env loading and merge precedence.
|
|
39
|
+
|
|
40
|
+
## Compatibility Model
|
|
41
|
+
|
|
42
|
+
### bird parity
|
|
43
|
+
- Frigatebird implements the `bird` CLI command/flag interface to maximize drop-in usability.
|
|
44
|
+
- GraphQL-only internals are represented as compatibility behavior where needed.
|
|
45
|
+
|
|
46
|
+
### x-list-manager parity
|
|
47
|
+
- Frigatebird embeds list automation commands (`add`, `remove`, `batch`, `refresh`) with equivalent UX expectations.
|
|
48
|
+
|
|
49
|
+
## Testing Strategy
|
|
50
|
+
|
|
51
|
+
- Unit tests cover parsing, invocation, handler orchestration, and client helper behavior.
|
|
52
|
+
- End-to-end tests run real browser flows against fixture pages for read-only scenarios and empty accounts.
|
|
53
|
+
- Release gate requires lint + build + full test + coverage.
|
|
54
|
+
|
|
55
|
+
## Operational Constraints
|
|
56
|
+
|
|
57
|
+
- X DOM/selectors can change without notice.
|
|
58
|
+
- Auth depends on valid browser session cookies or explicit token input.
|
|
59
|
+
- Read-only fixture e2e validates resilient behavior under low/no timeline data.
|
package/TASKS.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Frigatebird Parity and Release Tasks
|
|
2
|
+
|
|
3
|
+
## Parity
|
|
4
|
+
- [x] Match `bird` command surface from a CLI interface standpoint.
|
|
5
|
+
- [x] Keep shorthand tweet read invocation compatibility.
|
|
6
|
+
- [x] Implement pagination and JSON flags across read commands.
|
|
7
|
+
- [x] Support bird-style config/env precedence and key mappings.
|
|
8
|
+
- [x] Keep compatibility behavior for `query-ids` and bookmark expansion flags.
|
|
9
|
+
|
|
10
|
+
## x-list-manager Integration
|
|
11
|
+
- [x] Include `refresh`, `add`, `remove`, and `batch` commands.
|
|
12
|
+
- [x] Preserve headless/headed toggle behavior with `--no-headless`.
|
|
13
|
+
- [x] Keep result reporting compatible for automation use.
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
- [x] Split monolithic implementation into modular layers (`cli`, `commands`, `client`, `browser`, `lib`).
|
|
17
|
+
- [x] Define typed client interface for clear command contracts.
|
|
18
|
+
- [x] Centralize option parsing and output formatting.
|
|
19
|
+
|
|
20
|
+
## Testing
|
|
21
|
+
- [x] Add/expand unit tests for CLI entry, invocation normalization, options, handlers, and client helpers.
|
|
22
|
+
- [x] Add read-only end-to-end coverage for empty-account conditions.
|
|
23
|
+
- [x] Ensure lint/build/test/coverage all pass before release.
|
|
24
|
+
|
|
25
|
+
## Release Preparation
|
|
26
|
+
- [x] Add `CHANGELOG.md`.
|
|
27
|
+
- [x] Add `RELEASE.md` release checklist and publish flow.
|
|
28
|
+
- [x] Add `SKILL.md` + `SPEC.md` + `TASKS.md` for agent-oriented usage.
|
|
29
|
+
- [ ] Add mutation-focused e2e coverage against disposable test accounts (post-release hardening).
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getCookies } from "@steipete/sweet-cookie";
|
|
4
|
+
function normalizeDomain(domain) {
|
|
5
|
+
if (!domain)
|
|
6
|
+
return ".x.com";
|
|
7
|
+
return domain.startsWith(".") ? domain : `.${domain}`;
|
|
8
|
+
}
|
|
9
|
+
function toPlaywrightCookie(cookie) {
|
|
10
|
+
return {
|
|
11
|
+
name: cookie.name,
|
|
12
|
+
value: cookie.value,
|
|
13
|
+
domain: normalizeDomain(cookie.domain),
|
|
14
|
+
path: cookie.path ?? "/",
|
|
15
|
+
expires: cookie.expires ?? -1,
|
|
16
|
+
httpOnly: cookie.httpOnly ?? false,
|
|
17
|
+
secure: cookie.secure ?? true,
|
|
18
|
+
sameSite: cookie.sameSite ?? "Lax",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function mapSource(value) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
export class AuthStore {
|
|
25
|
+
authFile;
|
|
26
|
+
constructor(authFile = path.join(process.cwd(), "auth.json")) {
|
|
27
|
+
this.authFile = authFile;
|
|
28
|
+
}
|
|
29
|
+
loadFromDisk() {
|
|
30
|
+
if (!fs.existsSync(this.authFile))
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(fs.readFileSync(this.authFile, "utf8"));
|
|
34
|
+
if (!Array.isArray(parsed.cookies) || parsed.cookies.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
return {
|
|
37
|
+
cookies: parsed.cookies,
|
|
38
|
+
source: parsed.source ?? "auth.json",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
save(cookies, source) {
|
|
46
|
+
const payload = {
|
|
47
|
+
cookies,
|
|
48
|
+
source,
|
|
49
|
+
createdAt: new Date().toISOString(),
|
|
50
|
+
};
|
|
51
|
+
fs.writeFileSync(this.authFile, JSON.stringify(payload, null, 2));
|
|
52
|
+
}
|
|
53
|
+
clear() {
|
|
54
|
+
if (fs.existsSync(this.authFile)) {
|
|
55
|
+
fs.unlinkSync(this.authFile);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async extractFromBrowser(options) {
|
|
59
|
+
const browsers = options.cookieSource.map(mapSource);
|
|
60
|
+
const extraction = await getCookies({
|
|
61
|
+
url: "https://x.com",
|
|
62
|
+
browsers,
|
|
63
|
+
chromeProfile: options.chromeProfileDir ?? options.chromeProfile,
|
|
64
|
+
firefoxProfile: options.firefoxProfile,
|
|
65
|
+
timeoutMs: options.cookieTimeout,
|
|
66
|
+
}).catch(() => null);
|
|
67
|
+
if (!extraction || extraction.cookies.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const authToken = extraction.cookies.find((cookie) => cookie.name === "auth_token");
|
|
71
|
+
const ct0 = extraction.cookies.find((cookie) => cookie.name === "ct0");
|
|
72
|
+
if (!authToken || !ct0) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const playwrightCookies = [
|
|
76
|
+
toPlaywrightCookie({
|
|
77
|
+
name: "auth_token",
|
|
78
|
+
value: authToken.value,
|
|
79
|
+
domain: authToken.domain,
|
|
80
|
+
path: authToken.path,
|
|
81
|
+
expires: authToken.expires,
|
|
82
|
+
httpOnly: true,
|
|
83
|
+
secure: authToken.secure,
|
|
84
|
+
sameSite: authToken.sameSite,
|
|
85
|
+
}),
|
|
86
|
+
toPlaywrightCookie({
|
|
87
|
+
name: "ct0",
|
|
88
|
+
value: ct0.value,
|
|
89
|
+
domain: ct0.domain,
|
|
90
|
+
path: ct0.path,
|
|
91
|
+
expires: ct0.expires,
|
|
92
|
+
httpOnly: ct0.httpOnly,
|
|
93
|
+
secure: ct0.secure,
|
|
94
|
+
sameSite: ct0.sameSite,
|
|
95
|
+
}),
|
|
96
|
+
];
|
|
97
|
+
return {
|
|
98
|
+
cookies: playwrightCookies,
|
|
99
|
+
source: `browser:${options.cookieSource.join(",")}`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
fromManualTokens(options) {
|
|
103
|
+
if (!options.authToken || !options.ct0)
|
|
104
|
+
return null;
|
|
105
|
+
return {
|
|
106
|
+
source: "manual-flags",
|
|
107
|
+
cookies: [
|
|
108
|
+
toPlaywrightCookie({
|
|
109
|
+
name: "auth_token",
|
|
110
|
+
value: options.authToken,
|
|
111
|
+
domain: ".x.com",
|
|
112
|
+
path: "/",
|
|
113
|
+
httpOnly: true,
|
|
114
|
+
secure: true,
|
|
115
|
+
sameSite: "Lax",
|
|
116
|
+
}),
|
|
117
|
+
toPlaywrightCookie({
|
|
118
|
+
name: "ct0",
|
|
119
|
+
value: options.ct0,
|
|
120
|
+
domain: ".x.com",
|
|
121
|
+
path: "/",
|
|
122
|
+
httpOnly: false,
|
|
123
|
+
secure: true,
|
|
124
|
+
sameSite: "Lax",
|
|
125
|
+
}),
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async resolve(options, forceRefresh = false) {
|
|
130
|
+
const manual = this.fromManualTokens(options);
|
|
131
|
+
if (manual)
|
|
132
|
+
return manual;
|
|
133
|
+
if (!forceRefresh) {
|
|
134
|
+
const saved = this.loadFromDisk();
|
|
135
|
+
if (saved)
|
|
136
|
+
return saved;
|
|
137
|
+
}
|
|
138
|
+
const extracted = await this.extractFromBrowser(options);
|
|
139
|
+
if (extracted) {
|
|
140
|
+
this.save(extracted.cookies, extracted.source);
|
|
141
|
+
return extracted;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|