@dotta/xc 0.1.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.
Files changed (143) hide show
  1. package/README.md +348 -0
  2. package/dist/__tests__/bookmarks.test.d.ts +4 -0
  3. package/dist/__tests__/bookmarks.test.js +104 -0
  4. package/dist/__tests__/bookmarks.test.js.map +1 -0
  5. package/dist/__tests__/budget.test.d.ts +6 -0
  6. package/dist/__tests__/budget.test.js +105 -0
  7. package/dist/__tests__/budget.test.js.map +1 -0
  8. package/dist/__tests__/dm.test.d.ts +4 -0
  9. package/dist/__tests__/dm.test.js +115 -0
  10. package/dist/__tests__/dm.test.js.map +1 -0
  11. package/dist/__tests__/followers.test.d.ts +4 -0
  12. package/dist/__tests__/followers.test.js +129 -0
  13. package/dist/__tests__/followers.test.js.map +1 -0
  14. package/dist/__tests__/lib/api.test.d.ts +5 -0
  15. package/dist/__tests__/lib/api.test.js +202 -0
  16. package/dist/__tests__/lib/api.test.js.map +1 -0
  17. package/dist/__tests__/lib/budget.test.d.ts +5 -0
  18. package/dist/__tests__/lib/budget.test.js +194 -0
  19. package/dist/__tests__/lib/budget.test.js.map +1 -0
  20. package/dist/__tests__/lib/config.test.d.ts +6 -0
  21. package/dist/__tests__/lib/config.test.js +228 -0
  22. package/dist/__tests__/lib/config.test.js.map +1 -0
  23. package/dist/__tests__/lib/cost.test.d.ts +6 -0
  24. package/dist/__tests__/lib/cost.test.js +177 -0
  25. package/dist/__tests__/lib/cost.test.js.map +1 -0
  26. package/dist/__tests__/lib/format.test.d.ts +4 -0
  27. package/dist/__tests__/lib/format.test.js +139 -0
  28. package/dist/__tests__/lib/format.test.js.map +1 -0
  29. package/dist/__tests__/lib/oauth.test.d.ts +5 -0
  30. package/dist/__tests__/lib/oauth.test.js +123 -0
  31. package/dist/__tests__/lib/oauth.test.js.map +1 -0
  32. package/dist/__tests__/lib/resolve.test.d.ts +4 -0
  33. package/dist/__tests__/lib/resolve.test.js +154 -0
  34. package/dist/__tests__/lib/resolve.test.js.map +1 -0
  35. package/dist/__tests__/lists.test.d.ts +4 -0
  36. package/dist/__tests__/lists.test.js +96 -0
  37. package/dist/__tests__/lists.test.js.map +1 -0
  38. package/dist/__tests__/media.test.d.ts +4 -0
  39. package/dist/__tests__/media.test.js +132 -0
  40. package/dist/__tests__/media.test.js.map +1 -0
  41. package/dist/cli.d.ts +2 -0
  42. package/dist/cli.js +93 -0
  43. package/dist/cli.js.map +1 -0
  44. package/dist/commands/auth.d.ts +2 -0
  45. package/dist/commands/auth.js +191 -0
  46. package/dist/commands/auth.js.map +1 -0
  47. package/dist/commands/block.d.ts +15 -0
  48. package/dist/commands/block.js +117 -0
  49. package/dist/commands/block.js.map +1 -0
  50. package/dist/commands/bookmarks.d.ts +12 -0
  51. package/dist/commands/bookmarks.js +100 -0
  52. package/dist/commands/bookmarks.js.map +1 -0
  53. package/dist/commands/budget.d.ts +9 -0
  54. package/dist/commands/budget.js +124 -0
  55. package/dist/commands/budget.js.map +1 -0
  56. package/dist/commands/cost.d.ts +5 -0
  57. package/dist/commands/cost.js +75 -0
  58. package/dist/commands/cost.js.map +1 -0
  59. package/dist/commands/delete.d.ts +8 -0
  60. package/dist/commands/delete.js +31 -0
  61. package/dist/commands/delete.js.map +1 -0
  62. package/dist/commands/dm.d.ts +10 -0
  63. package/dist/commands/dm.js +179 -0
  64. package/dist/commands/dm.js.map +1 -0
  65. package/dist/commands/engagement.d.ts +14 -0
  66. package/dist/commands/engagement.js +167 -0
  67. package/dist/commands/engagement.js.map +1 -0
  68. package/dist/commands/followers.d.ts +14 -0
  69. package/dist/commands/followers.js +138 -0
  70. package/dist/commands/followers.js.map +1 -0
  71. package/dist/commands/get.d.ts +2 -0
  72. package/dist/commands/get.js +63 -0
  73. package/dist/commands/get.js.map +1 -0
  74. package/dist/commands/hide.d.ts +10 -0
  75. package/dist/commands/hide.js +58 -0
  76. package/dist/commands/hide.js.map +1 -0
  77. package/dist/commands/like.d.ts +3 -0
  78. package/dist/commands/like.js +52 -0
  79. package/dist/commands/like.js.map +1 -0
  80. package/dist/commands/lists.d.ts +20 -0
  81. package/dist/commands/lists.js +384 -0
  82. package/dist/commands/lists.js.map +1 -0
  83. package/dist/commands/media.d.ts +19 -0
  84. package/dist/commands/media.js +205 -0
  85. package/dist/commands/media.js.map +1 -0
  86. package/dist/commands/mentions.d.ts +8 -0
  87. package/dist/commands/mentions.js +59 -0
  88. package/dist/commands/mentions.js.map +1 -0
  89. package/dist/commands/mute.d.ts +12 -0
  90. package/dist/commands/mute.js +99 -0
  91. package/dist/commands/mute.js.map +1 -0
  92. package/dist/commands/post.d.ts +11 -0
  93. package/dist/commands/post.js +87 -0
  94. package/dist/commands/post.js.map +1 -0
  95. package/dist/commands/repost.d.ts +10 -0
  96. package/dist/commands/repost.js +59 -0
  97. package/dist/commands/repost.js.map +1 -0
  98. package/dist/commands/search.d.ts +2 -0
  99. package/dist/commands/search.js +49 -0
  100. package/dist/commands/search.js.map +1 -0
  101. package/dist/commands/stream.d.ts +13 -0
  102. package/dist/commands/stream.js +251 -0
  103. package/dist/commands/stream.js.map +1 -0
  104. package/dist/commands/timeline.d.ts +2 -0
  105. package/dist/commands/timeline.js +61 -0
  106. package/dist/commands/timeline.js.map +1 -0
  107. package/dist/commands/trends.d.ts +10 -0
  108. package/dist/commands/trends.js +59 -0
  109. package/dist/commands/trends.js.map +1 -0
  110. package/dist/commands/usage.d.ts +2 -0
  111. package/dist/commands/usage.js +52 -0
  112. package/dist/commands/usage.js.map +1 -0
  113. package/dist/commands/user.d.ts +2 -0
  114. package/dist/commands/user.js +43 -0
  115. package/dist/commands/user.js.map +1 -0
  116. package/dist/commands/usersearch.d.ts +8 -0
  117. package/dist/commands/usersearch.js +48 -0
  118. package/dist/commands/usersearch.js.map +1 -0
  119. package/dist/commands/whoami.d.ts +2 -0
  120. package/dist/commands/whoami.js +54 -0
  121. package/dist/commands/whoami.js.map +1 -0
  122. package/dist/lib/api.d.ts +12 -0
  123. package/dist/lib/api.js +91 -0
  124. package/dist/lib/api.js.map +1 -0
  125. package/dist/lib/budget.d.ts +44 -0
  126. package/dist/lib/budget.js +119 -0
  127. package/dist/lib/budget.js.map +1 -0
  128. package/dist/lib/config.d.ts +39 -0
  129. package/dist/lib/config.js +63 -0
  130. package/dist/lib/config.js.map +1 -0
  131. package/dist/lib/cost.d.ts +43 -0
  132. package/dist/lib/cost.js +224 -0
  133. package/dist/lib/cost.js.map +1 -0
  134. package/dist/lib/format.d.ts +24 -0
  135. package/dist/lib/format.js +72 -0
  136. package/dist/lib/format.js.map +1 -0
  137. package/dist/lib/oauth.d.ts +32 -0
  138. package/dist/lib/oauth.js +132 -0
  139. package/dist/lib/oauth.js.map +1 -0
  140. package/dist/lib/resolve.d.ts +12 -0
  141. package/dist/lib/resolve.js +48 -0
  142. package/dist/lib/resolve.js.map +1 -0
  143. package/package.json +46 -0
package/README.md ADDED
@@ -0,0 +1,348 @@
1
+ # xc — X API CLI
2
+
3
+ CLI client for the [X API v2](https://docs.x.com/x-api/introduction). Pay-per-use, no cookie scraping. Built on the official [@xdevplatform/xdk](https://github.com/xdevplatform/xdk) SDK with OAuth 2.0 PKCE.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @dotta/xc
9
+ ```
10
+
11
+ Requires Node.js >= 18.
12
+
13
+ ## Agent Skill
14
+
15
+ xc includes an [agent skill](skills/xc-cli/SKILL.md) so your agent can use `xc` on your behalf.
16
+
17
+ ```bash
18
+ npx skills add https://github.com/cryppadotta/xc --skill xc-cli
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ Get a Bearer Token from [console.x.com](https://console.x.com) and start reading immediately:
24
+
25
+ ```bash
26
+ xc auth token <BEARER_TOKEN>
27
+ xc get https://x.com/dotta/status/1612500057768755201
28
+ ```
29
+
30
+ Bearer tokens are read-only. For posting, liking, following, and other write operations, see [Full OAuth Setup](#full-oauth-setup) below.
31
+
32
+ ## Commands
33
+
34
+ ### Reading
35
+
36
+ ```bash
37
+ xc get <post-id> # Get a post by ID
38
+ xc get https://x.com/dotta/status/1612500057768755201 # Or by URL
39
+ xc search "typescript" # Search recent posts (last 7 days)
40
+ xc search "from:dotta" -n 20 # Search by author
41
+ xc search "AI" --archive # Full archive search (if your plan supports it)
42
+ xc user dotta # Look up a user by @username
43
+ xc usersearch "keyword" # Search for users by keyword
44
+ xc timeline # Your home timeline
45
+ xc timeline dotta # A specific user's posts
46
+ xc mentions # Your mentions
47
+ xc mentions dotta # Another user's mentions
48
+ xc whoami # Show authenticated user
49
+ ```
50
+
51
+ ### Posting
52
+
53
+ ```bash
54
+ xc post "Hello world" # Create a post
55
+ xc post "Reply" --reply 123456 # Reply to a post
56
+ xc post "Check this" --quote 123 # Quote a post
57
+ xc post "First" --thread "Second" "Third" # Post a thread
58
+ xc post "Photo" --media photo.jpg # Post with media attachment
59
+ xc post "text" --json # Show raw response
60
+ xc delete 1234567890 # Delete a post
61
+ ```
62
+
63
+ ### Likes & Reposts
64
+
65
+ ```bash
66
+ xc like 1234567890 # Like a post by ID
67
+ xc unlike 1234567890 # Unlike a post
68
+ xc repost 1234567890 # Repost a post
69
+ xc unrepost 1234567890 # Undo a repost
70
+ ```
71
+
72
+ ### Engagement Lookups
73
+
74
+ ```bash
75
+ xc quotes 1234567890 # List quote tweets of a post
76
+ xc likes 1234567890 # List users who liked a post
77
+ xc reposts 1234567890 # List users who reposted a post
78
+ xc liked # Posts you've liked
79
+ xc liked username # Posts liked by a user
80
+ ```
81
+
82
+ ### Reply Moderation
83
+
84
+ ```bash
85
+ xc hide 1234567890 # Hide a reply on your post
86
+ xc unhide 1234567890 # Unhide a reply
87
+ ```
88
+
89
+ ### Bookmarks
90
+
91
+ ```bash
92
+ xc bookmarks # List your bookmarks
93
+ xc bookmark 1234567890 # Bookmark a post
94
+ xc unbookmark 1234567890 # Remove bookmark
95
+ ```
96
+
97
+ ### Blocks & Mutes
98
+
99
+ ```bash
100
+ xc block username # Block a user
101
+ xc unblock username # Unblock a user
102
+ xc blocked # List blocked users
103
+ xc blocked --json # JSON output
104
+ xc mute username # Mute a user
105
+ xc unmute username # Unmute a user
106
+ xc muted # List muted users
107
+ ```
108
+
109
+ ### Lists
110
+
111
+ ```bash
112
+ xc lists # List your owned lists
113
+ xc list view 1234567890 # View posts in a list
114
+ xc list create "My List" # Create a new list
115
+ xc list create "Secret" --private --description "My private list"
116
+ xc list update 123 --name "New Name" --description "Updated"
117
+ xc list update 123 --public # Make public
118
+ xc list delete 1234567890 # Delete a list
119
+ xc list members 1234567890 # List members
120
+ xc list add 123 username # Add a member
121
+ xc list remove 123 username # Remove a member
122
+ xc list follow 1234567890 # Follow a list
123
+ xc list unfollow 1234567890 # Unfollow a list
124
+ xc list pin 1234567890 # Pin a list
125
+ xc list unpin 1234567890 # Unpin a list
126
+ ```
127
+
128
+ ### Followers
129
+
130
+ ```bash
131
+ xc followers dotta # List followers of a user
132
+ xc followers dotta --limit 50
133
+ xc following dotta # List who a user follows
134
+ xc follow dotta # Follow a user
135
+ xc unfollow dotta # Unfollow a user
136
+ ```
137
+
138
+ ### Trends
139
+
140
+ ```bash
141
+ xc trends # Personalized trending topics
142
+ xc trends --global # Worldwide trends
143
+ xc trends 2459115 # Trends by location (WOEID)
144
+ xc trends --json # JSON output
145
+ ```
146
+
147
+ ### Direct Messages
148
+
149
+ ```bash
150
+ xc dm list # List recent DM conversations
151
+ xc dm history username # View DM history with a user
152
+ xc dm send username "Hello" # Send a DM
153
+ ```
154
+
155
+ ### Media
156
+
157
+ ```bash
158
+ xc media upload photo.jpg # Upload media, returns media_id
159
+ # Then use with post:
160
+ xc post "Check this out" --media photo.jpg
161
+ ```
162
+
163
+ ### Streaming
164
+
165
+ ```bash
166
+ xc stream rules # List current stream rules
167
+ xc stream add "AI OR LLM" # Add a filtered stream rule
168
+ xc stream remove <rule-id> # Remove a rule by ID
169
+ xc stream clear # Remove all rules
170
+ xc stream connect # Connect to stream (outputs posts in real-time)
171
+ xc stream connect --json # Raw JSON stream output
172
+ ```
173
+
174
+ ## Full OAuth Setup
175
+
176
+ For write operations (posting, liking, following, DMs, etc.), you need OAuth 2.0 with PKCE.
177
+
178
+ ### Getting a Client ID
179
+
180
+ 1. Go to [developer.x.com](https://developer.x.com) (existing apps) or [console.x.com](https://console.x.com) (new projects)
181
+ 2. Create or select an app
182
+ 3. Under **OAuth 2.0 settings**, copy your **Client ID** (and optionally your **Client Secret** for confidential clients)
183
+ 4. Set the **Callback URL** to `http://127.0.0.1:3391/callback`
184
+ 5. Enable the required scopes (xc requests all of these automatically):
185
+ - `tweet.read`, `tweet.write` — read/write posts
186
+ - `tweet.moderate.write` — hide/unhide replies
187
+ - `users.read` — look up users
188
+ - `follows.read`, `follows.write` — manage follows
189
+ - `like.read`, `like.write` — manage likes
190
+ - `list.read`, `list.write` — manage lists
191
+ - `bookmark.read`, `bookmark.write` — manage bookmarks
192
+ - `block.read`, `block.write` — manage blocks
193
+ - `mute.read`, `mute.write` — manage mutes
194
+ - `dm.read`, `dm.write` — read/send DMs
195
+ - `media.write` — upload media
196
+ - `offline.access` — refresh tokens
197
+
198
+ ### Login
199
+
200
+ ```bash
201
+ # Interactive OAuth login (opens browser)
202
+ xc auth login --client-id <YOUR_CLIENT_ID>
203
+
204
+ # With client secret (for confidential apps — enables token refresh)
205
+ xc auth login --client-id <YOUR_CLIENT_ID> --client-secret <YOUR_SECRET>
206
+
207
+ # Check auth status
208
+ xc auth status
209
+
210
+ # Logout
211
+ xc auth logout
212
+ ```
213
+
214
+ ### Multiple Accounts
215
+
216
+ ```bash
217
+ # Login with a named account
218
+ xc auth login --account work --client-id <CLIENT_ID>
219
+
220
+ # Switch default account
221
+ xc auth switch work
222
+
223
+ # Use a specific account for one command
224
+ xc search "query" --account work
225
+ ```
226
+
227
+ Credentials are stored in `~/.xc/config.json` (or `$XC_CONFIG_DIR/config.json`). Legacy `~/.config/xc/` configs are auto-migrated.
228
+
229
+ ## Cost Tracking
230
+
231
+ Every API call is logged to `~/.xc/usage.jsonl` with timestamp, endpoint, method, and estimated cost. A cost footer is appended to every command's output.
232
+
233
+ ```bash
234
+ xc cost # Cost summary (1h, 24h, 7d, 30d)
235
+ xc cost --daily # Day-by-day breakdown
236
+ xc cost --json # Machine-readable summary
237
+ xc cost log # Raw request log (last 20)
238
+ xc cost log --limit 50 # More entries
239
+ xc cost log --json # Raw JSON log
240
+ ```
241
+
242
+ Suppress the per-command cost footer with `--quiet`:
243
+
244
+ ```bash
245
+ xc search "query" --quiet
246
+ ```
247
+
248
+ ### API Usage Stats
249
+
250
+ ```bash
251
+ xc usage # X API usage stats (tweet caps, etc.)
252
+ xc usage --json
253
+ ```
254
+
255
+ ## Budget Enforcement
256
+
257
+ Set daily spending limits to avoid surprise costs. Budget config lives in `~/.xc/budget.json`.
258
+
259
+ ### Setting a Budget
260
+
261
+ ```bash
262
+ xc budget set --daily 2.00 # Warn when over $2/day
263
+ xc budget set --daily 5.00 --action block # Block requests over $5/day
264
+ xc budget set --daily 1.00 --action confirm # Ask for confirmation when over
265
+ ```
266
+
267
+ **Actions:**
268
+ - `warn` (default) — print a warning but allow the request
269
+ - `block` — reject the request with an error
270
+ - `confirm` — prompt interactively before proceeding
271
+
272
+ ### Viewing Budget Status
273
+
274
+ ```bash
275
+ xc budget show
276
+ # Budget:
277
+ #
278
+ # Daily limit: $2.00
279
+ # Today spent: $0.45 (22%)
280
+ # Remaining: $1.55
281
+ # Action: warn
282
+ # Locked: no
283
+ ```
284
+
285
+ ### Password Protection
286
+
287
+ Lock your budget so it can't be changed without a password:
288
+
289
+ ```bash
290
+ xc budget lock --password mysecret
291
+
292
+ # Now set/reset require --password
293
+ xc budget set --daily 10.00 --password mysecret
294
+ xc budget reset --password mysecret
295
+
296
+ # Remove the lock
297
+ xc budget unlock --password mysecret
298
+ ```
299
+
300
+ `show` and `cost` never require a password — only `set` and `reset` do.
301
+
302
+ ### Removing Budget
303
+
304
+ ```bash
305
+ xc budget reset # Remove budget config
306
+ xc budget reset --password pass # If locked
307
+ ```
308
+
309
+ ## Config
310
+
311
+ All configuration is stored in `~/.xc/` (or `$XC_CONFIG_DIR`):
312
+
313
+ | File | Contents |
314
+ |------|----------|
315
+ | `config.json` | Auth credentials (OAuth tokens, accounts) |
316
+ | `budget.json` | Budget limits and password lock |
317
+ | `usage.jsonl` | API request cost log (append-only) |
318
+
319
+ Legacy `~/.config/xc/` configs are auto-migrated on first run.
320
+
321
+ ## Development
322
+
323
+ ```bash
324
+ git clone https://github.com/cryppadotta/xc.git
325
+ cd xc
326
+ pnpm install
327
+ pnpm build
328
+ npm link # makes `xc` available globally
329
+
330
+ pnpm dev -- <command> # Run without building
331
+ pnpm build # Compile TypeScript
332
+ pnpm lint # Type check
333
+ pnpm test # Run tests (vitest)
334
+ ```
335
+
336
+ ## Global Flags
337
+
338
+ | Flag | Description |
339
+ |------|-------------|
340
+ | `--quiet` | Suppress cost footer |
341
+ | `--json` | Raw JSON output (most commands) |
342
+ | `--account <name>` | Use a specific named account |
343
+ | `-V, --version` | Show version |
344
+ | `-h, --help` | Show help |
345
+
346
+ ## License
347
+
348
+ MIT — forked from [jalehman/xc](https://github.com/jalehman/xc).
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for bookmarks commands with mocked SDK client.
3
+ */
4
+ export {};
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Tests for bookmarks commands with mocked SDK client.
3
+ */
4
+ import { describe, it, expect, vi, beforeEach } from "vitest";
5
+ import { Command } from "commander";
6
+ import { registerBookmarksCommand, registerBookmarkCommand, registerUnbookmarkCommand, } from "../commands/bookmarks.js";
7
+ vi.mock("../lib/api.js", () => ({
8
+ getClient: vi.fn(),
9
+ }));
10
+ vi.mock("../lib/resolve.js", () => ({
11
+ resolveAuthenticatedUserId: vi.fn(),
12
+ resolveUserId: vi.fn(),
13
+ }));
14
+ vi.mock("../lib/cost.js", () => ({
15
+ logApiCall: vi.fn(),
16
+ formatCostFooter: vi.fn(() => ""),
17
+ estimateCost: vi.fn(() => 0),
18
+ loadUsageLog: vi.fn(() => []),
19
+ computeTodaySpend: vi.fn(() => 0),
20
+ }));
21
+ vi.mock("../lib/budget.js", () => ({
22
+ checkBudget: vi.fn(),
23
+ loadBudget: vi.fn(() => ({ action: "warn" })),
24
+ }));
25
+ import { getClient } from "../lib/api.js";
26
+ import { resolveAuthenticatedUserId } from "../lib/resolve.js";
27
+ describe("bookmarks command", () => {
28
+ let program;
29
+ beforeEach(() => {
30
+ program = new Command();
31
+ program.exitOverride();
32
+ registerBookmarksCommand(program);
33
+ vi.clearAllMocks();
34
+ });
35
+ it("lists bookmarks", async () => {
36
+ vi.mocked(resolveAuthenticatedUserId).mockResolvedValue("myid");
37
+ const mockGetBookmarks = vi.fn().mockResolvedValue({
38
+ data: [
39
+ {
40
+ id: "t1",
41
+ text: "bookmarked post",
42
+ authorId: "u1",
43
+ createdAt: "2025-01-01T00:00:00Z",
44
+ },
45
+ ],
46
+ includes: {
47
+ users: [{ id: "u1", username: "alice", name: "Alice" }],
48
+ },
49
+ });
50
+ vi.mocked(getClient).mockResolvedValue({
51
+ users: { getBookmarks: mockGetBookmarks },
52
+ });
53
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => { });
54
+ await program.parseAsync(["node", "xc", "bookmarks"]);
55
+ expect(mockGetBookmarks).toHaveBeenCalledWith("myid", expect.objectContaining({
56
+ maxResults: 20,
57
+ }));
58
+ expect(logSpy).toHaveBeenCalledWith("Bookmarks:\n");
59
+ logSpy.mockRestore();
60
+ });
61
+ });
62
+ describe("bookmark command", () => {
63
+ let program;
64
+ beforeEach(() => {
65
+ program = new Command();
66
+ program.exitOverride();
67
+ registerBookmarkCommand(program);
68
+ vi.clearAllMocks();
69
+ });
70
+ it("adds a bookmark", async () => {
71
+ vi.mocked(resolveAuthenticatedUserId).mockResolvedValue("myid");
72
+ const mockCreate = vi.fn().mockResolvedValue({ data: { bookmarked: true } });
73
+ vi.mocked(getClient).mockResolvedValue({
74
+ users: { createBookmark: mockCreate },
75
+ });
76
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => { });
77
+ await program.parseAsync(["node", "xc", "bookmark", "12345"]);
78
+ expect(mockCreate).toHaveBeenCalledWith("myid", { tweetId: "12345" });
79
+ expect(logSpy).toHaveBeenCalledWith("Bookmarked post 12345");
80
+ logSpy.mockRestore();
81
+ });
82
+ });
83
+ describe("unbookmark command", () => {
84
+ let program;
85
+ beforeEach(() => {
86
+ program = new Command();
87
+ program.exitOverride();
88
+ registerUnbookmarkCommand(program);
89
+ vi.clearAllMocks();
90
+ });
91
+ it("removes a bookmark", async () => {
92
+ vi.mocked(resolveAuthenticatedUserId).mockResolvedValue("myid");
93
+ const mockDelete = vi.fn().mockResolvedValue({ data: { bookmarked: false } });
94
+ vi.mocked(getClient).mockResolvedValue({
95
+ users: { deleteBookmark: mockDelete },
96
+ });
97
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => { });
98
+ await program.parseAsync(["node", "xc", "unbookmark", "12345"]);
99
+ expect(mockDelete).toHaveBeenCalledWith("myid", "12345");
100
+ expect(logSpy).toHaveBeenCalledWith("Unbookmarked post 12345");
101
+ logSpy.mockRestore();
102
+ });
103
+ });
104
+ //# sourceMappingURL=bookmarks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bookmarks.test.js","sourceRoot":"","sources":["../../src/__tests__/bookmarks.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,yBAAyB,GAC1B,MAAM,0BAA0B,CAAC;AAElC,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9B,SAAS,EAAE,EAAE,CAAC,EAAE,EAAE;CACnB,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;IAClC,0BAA0B,EAAE,EAAE,CAAC,EAAE,EAAE;IACnC,aAAa,EAAE,EAAE,CAAC,EAAE,EAAE;CACvB,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/B,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;IACnB,gBAAgB,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;IACjC,YAAY,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC5B,YAAY,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;IAC7B,iBAAiB,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;CAClC,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;IACjC,WAAW,EAAE,EAAE,CAAC,EAAE,EAAE;IACpB,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAC9C,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAE/D,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,IAAI,OAAgB,CAAC;IAErB,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QACxB,OAAO,CAAC,YAAY,EAAE,CAAC;QACvB,wBAAwB,CAAC,OAAO,CAAC,CAAC;QAClC,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;QAC/B,EAAE,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAChE,MAAM,gBAAgB,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC;YACjD,IAAI,EAAE;gBACJ;oBACE,EAAE,EAAE,IAAI;oBACR,IAAI,EAAE,iBAAiB;oBACvB,QAAQ,EAAE,IAAI;oBACd,SAAS,EAAE,sBAAsB;iBAClC;aACF;YACD,QAAQ,EAAE;gBACR,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;aACxD;SACF,CAAC,CAAC;QAEH,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,iBAAiB,CAAC;YACrC,KAAK,EAAE,EAAE,YAAY,EAAE,gBAAgB,EAAE;SACU,CAAC,CAAC;QAEvD,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;QAEtD,MAAM,CAAC,gBAAgB,CAAC,CAAC,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,gBAAgB,CAAC;YAC5E,UAAU,EAAE,EAAE;SACf,CAAC,CAAC,CAAC;QACJ,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC;QACpD,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,IAAI,OAAgB,CAAC;IAErB,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QACxB,OAAO,CAAC,YAAY,EAAE,CAAC;QACvB,uBAAuB,CAAC,OAAO,CAAC,CAAC;QACjC,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iBAAiB,EAAE,KAAK,IAAI,EAAE;QAC/B,EAAE,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAChE,MAAM,UAAU,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;QAE7E,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,iBAAiB,CAAC;YACrC,KAAK,EAAE,EAAE,cAAc,EAAE,UAAU,EAAE;SACc,CAAC,CAAC;QAEvD,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QAE9D,MAAM,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACtE,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,uBAAuB,CAAC,CAAC;QAC7D,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,IAAI,OAAgB,CAAC;IAErB,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QACxB,OAAO,CAAC,YAAY,EAAE,CAAC;QACvB,yBAAyB,CAAC,OAAO,CAAC,CAAC;QACnC,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;QAClC,EAAE,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAChE,MAAM,UAAU,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAE9E,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,iBAAiB,CAAC;YACrC,KAAK,EAAE,EAAE,cAAc,EAAE,UAAU,EAAE;SACc,CAAC,CAAC;QAEvD,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;QAEhE,MAAM,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,yBAAyB,CAAC,CAAC;QAC/D,MAAM,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for budget lib: password locking, hashing, verification.
3
+ * Uses vi.resetModules() + dynamic import so the config module's
4
+ * CONFIG_DIR constant picks up the temp dir from process.env.
5
+ */
6
+ export {};
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Tests for budget lib: password locking, hashing, verification.
3
+ * Uses vi.resetModules() + dynamic import so the config module's
4
+ * CONFIG_DIR constant picks up the temp dir from process.env.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import os from "node:os";
10
+ let origConfigDir;
11
+ let tmpDir;
12
+ let budget;
13
+ beforeEach(async () => {
14
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "xc-budget-test-"));
15
+ origConfigDir = process.env.XC_CONFIG_DIR;
16
+ process.env.XC_CONFIG_DIR = tmpDir;
17
+ vi.resetModules();
18
+ budget = await import("../lib/budget.js");
19
+ });
20
+ afterEach(() => {
21
+ if (origConfigDir !== undefined) {
22
+ process.env.XC_CONFIG_DIR = origConfigDir;
23
+ }
24
+ else {
25
+ delete process.env.XC_CONFIG_DIR;
26
+ }
27
+ fs.rmSync(tmpDir, { recursive: true, force: true });
28
+ });
29
+ describe("budget basics", () => {
30
+ it("returns defaults when no budget file exists", () => {
31
+ const b = budget.loadBudget();
32
+ expect(b.action).toBe("warn");
33
+ expect(b.daily).toBeUndefined();
34
+ });
35
+ it("saves and loads budget config", () => {
36
+ budget.saveBudget({ daily: 5.0, action: "block" });
37
+ const b = budget.loadBudget();
38
+ expect(b.daily).toBe(5.0);
39
+ expect(b.action).toBe("block");
40
+ });
41
+ it("resets budget by removing file", () => {
42
+ budget.saveBudget({ daily: 5.0, action: "warn" });
43
+ expect(fs.existsSync(budget.getBudgetPath())).toBe(true);
44
+ budget.resetBudget();
45
+ expect(fs.existsSync(budget.getBudgetPath())).toBe(false);
46
+ });
47
+ });
48
+ describe("password hashing", () => {
49
+ it("produces consistent hashes with same salt", () => {
50
+ const salt = "abcdef1234567890";
51
+ const h1 = budget.hashPassword("mypassword", salt);
52
+ const h2 = budget.hashPassword("mypassword", salt);
53
+ expect(h1).toBe(h2);
54
+ });
55
+ it("produces different hashes with different salts", () => {
56
+ const h1 = budget.hashPassword("mypassword", "salt1");
57
+ const h2 = budget.hashPassword("mypassword", "salt2");
58
+ expect(h1).not.toBe(h2);
59
+ });
60
+ it("produces different hashes for different passwords", () => {
61
+ const salt = "samesalt";
62
+ const h1 = budget.hashPassword("password1", salt);
63
+ const h2 = budget.hashPassword("password2", salt);
64
+ expect(h1).not.toBe(h2);
65
+ });
66
+ });
67
+ describe("budget locking", () => {
68
+ it("reports unlocked when no password set", () => {
69
+ budget.saveBudget({ daily: 5.0, action: "warn" });
70
+ expect(budget.isLocked()).toBe(false);
71
+ });
72
+ it("locks budget with password", () => {
73
+ budget.saveBudget({ daily: 5.0, action: "warn" });
74
+ budget.lockBudget("secret123");
75
+ expect(budget.isLocked()).toBe(true);
76
+ });
77
+ it("verifies correct password", () => {
78
+ budget.saveBudget({ daily: 5.0, action: "warn" });
79
+ budget.lockBudget("secret123");
80
+ expect(budget.verifyPassword("secret123")).toBe(true);
81
+ });
82
+ it("rejects incorrect password", () => {
83
+ budget.saveBudget({ daily: 5.0, action: "warn" });
84
+ budget.lockBudget("secret123");
85
+ expect(budget.verifyPassword("wrong")).toBe(false);
86
+ });
87
+ it("unlocks budget and removes password", () => {
88
+ budget.saveBudget({ daily: 5.0, action: "warn" });
89
+ budget.lockBudget("secret123");
90
+ expect(budget.isLocked()).toBe(true);
91
+ budget.unlockBudget();
92
+ expect(budget.isLocked()).toBe(false);
93
+ expect(budget.verifyPassword("anything")).toBe(true);
94
+ });
95
+ it("preserves budget config when locking", () => {
96
+ budget.saveBudget({ daily: 10.0, action: "block" });
97
+ budget.lockBudget("mypass");
98
+ const b = budget.loadBudget();
99
+ expect(b.daily).toBe(10.0);
100
+ expect(b.action).toBe("block");
101
+ expect(b.passwordHash).toBeDefined();
102
+ expect(b.passwordSalt).toBeDefined();
103
+ });
104
+ });
105
+ //# sourceMappingURL=budget.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.test.js","sourceRoot":"","sources":["../../src/__tests__/budget.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for DM command registration and mock SDK interactions.
3
+ */
4
+ export {};