@jackwener/opencli 1.5.0 → 1.5.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 (108) hide show
  1. package/dist/browser/cdp.js +5 -0
  2. package/dist/browser/discover.js +11 -7
  3. package/dist/browser/index.d.ts +2 -0
  4. package/dist/browser/index.js +2 -0
  5. package/dist/browser/page.d.ts +4 -0
  6. package/dist/browser/page.js +52 -3
  7. package/dist/browser.test.js +5 -0
  8. package/dist/cli-manifest.json +460 -1
  9. package/dist/cli.js +34 -3
  10. package/dist/clis/apple-podcasts/commands.test.js +26 -3
  11. package/dist/clis/apple-podcasts/top.js +4 -1
  12. package/dist/clis/bluesky/feeds.yaml +29 -0
  13. package/dist/clis/bluesky/followers.yaml +33 -0
  14. package/dist/clis/bluesky/following.yaml +33 -0
  15. package/dist/clis/bluesky/profile.yaml +27 -0
  16. package/dist/clis/bluesky/search.yaml +34 -0
  17. package/dist/clis/bluesky/starter-packs.yaml +34 -0
  18. package/dist/clis/bluesky/thread.yaml +32 -0
  19. package/dist/clis/bluesky/trending.yaml +27 -0
  20. package/dist/clis/bluesky/user.yaml +34 -0
  21. package/dist/clis/twitter/trending.js +29 -61
  22. package/dist/clis/weread/shelf.js +132 -9
  23. package/dist/clis/weread/utils.js +5 -1
  24. package/dist/clis/xiaohongshu/publish.js +78 -42
  25. package/dist/clis/xiaohongshu/publish.test.js +20 -8
  26. package/dist/clis/xiaohongshu/search.d.ts +8 -1
  27. package/dist/clis/xiaohongshu/search.js +20 -1
  28. package/dist/clis/xiaohongshu/search.test.d.ts +1 -1
  29. package/dist/clis/xiaohongshu/search.test.js +32 -1
  30. package/dist/daemon.js +1 -0
  31. package/dist/discovery.js +40 -28
  32. package/dist/doctor.d.ts +1 -2
  33. package/dist/doctor.js +9 -5
  34. package/dist/engine.test.js +42 -0
  35. package/dist/errors.d.ts +1 -1
  36. package/dist/errors.js +2 -2
  37. package/dist/execution.js +45 -13
  38. package/dist/execution.test.d.ts +1 -0
  39. package/dist/execution.test.js +40 -0
  40. package/dist/extension-manifest-regression.test.d.ts +1 -0
  41. package/dist/extension-manifest-regression.test.js +12 -0
  42. package/dist/external.js +6 -1
  43. package/dist/main.js +1 -0
  44. package/dist/plugin-scaffold.d.ts +28 -0
  45. package/dist/plugin-scaffold.js +142 -0
  46. package/dist/plugin-scaffold.test.d.ts +4 -0
  47. package/dist/plugin-scaffold.test.js +83 -0
  48. package/dist/plugin.d.ts +55 -17
  49. package/dist/plugin.js +706 -154
  50. package/dist/plugin.test.js +836 -38
  51. package/dist/runtime.d.ts +1 -0
  52. package/dist/runtime.js +1 -1
  53. package/dist/types.d.ts +2 -0
  54. package/dist/weread-private-api-regression.test.js +185 -0
  55. package/docs/adapters/browser/bluesky.md +53 -0
  56. package/docs/guide/plugins.md +10 -0
  57. package/extension/dist/background.js +4 -2
  58. package/extension/manifest.json +4 -1
  59. package/extension/package-lock.json +2 -2
  60. package/extension/package.json +1 -1
  61. package/extension/src/background.ts +2 -1
  62. package/package.json +1 -1
  63. package/src/browser/cdp.ts +6 -0
  64. package/src/browser/discover.ts +10 -7
  65. package/src/browser/index.ts +2 -0
  66. package/src/browser/page.ts +49 -3
  67. package/src/browser.test.ts +6 -0
  68. package/src/cli.ts +34 -3
  69. package/src/clis/apple-podcasts/commands.test.ts +30 -2
  70. package/src/clis/apple-podcasts/top.ts +4 -1
  71. package/src/clis/bluesky/feeds.yaml +29 -0
  72. package/src/clis/bluesky/followers.yaml +33 -0
  73. package/src/clis/bluesky/following.yaml +33 -0
  74. package/src/clis/bluesky/profile.yaml +27 -0
  75. package/src/clis/bluesky/search.yaml +34 -0
  76. package/src/clis/bluesky/starter-packs.yaml +34 -0
  77. package/src/clis/bluesky/thread.yaml +32 -0
  78. package/src/clis/bluesky/trending.yaml +27 -0
  79. package/src/clis/bluesky/user.yaml +34 -0
  80. package/src/clis/twitter/trending.ts +29 -77
  81. package/src/clis/weread/shelf.ts +169 -9
  82. package/src/clis/weread/utils.ts +6 -1
  83. package/src/clis/xiaohongshu/publish.test.ts +22 -8
  84. package/src/clis/xiaohongshu/publish.ts +93 -52
  85. package/src/clis/xiaohongshu/search.test.ts +39 -1
  86. package/src/clis/xiaohongshu/search.ts +19 -1
  87. package/src/daemon.ts +1 -0
  88. package/src/discovery.ts +41 -33
  89. package/src/doctor.ts +11 -8
  90. package/src/engine.test.ts +38 -0
  91. package/src/errors.ts +6 -2
  92. package/src/execution.test.ts +47 -0
  93. package/src/execution.ts +39 -15
  94. package/src/extension-manifest-regression.test.ts +17 -0
  95. package/src/external.ts +6 -1
  96. package/src/main.ts +1 -0
  97. package/src/plugin-scaffold.test.ts +98 -0
  98. package/src/plugin-scaffold.ts +170 -0
  99. package/src/plugin.test.ts +881 -38
  100. package/src/plugin.ts +871 -158
  101. package/src/runtime.ts +2 -2
  102. package/src/types.ts +2 -0
  103. package/src/weread-private-api-regression.test.ts +207 -0
  104. package/tests/e2e/browser-public.test.ts +1 -1
  105. package/tests/e2e/output-formats.test.ts +10 -14
  106. package/tests/e2e/plugin-management.test.ts +4 -1
  107. package/tests/e2e/public-commands.test.ts +12 -1
  108. package/vitest.config.ts +1 -15
@@ -135,6 +135,7 @@ export class CDPBridge {
135
135
  class CDPPage {
136
136
  bridge;
137
137
  _pageEnabled = false;
138
+ _lastUrl = null;
138
139
  constructor(bridge) {
139
140
  this.bridge = bridge;
140
141
  }
@@ -146,6 +147,7 @@ class CDPPage {
146
147
  const loadPromise = this.bridge.waitForEvent('Page.loadEventFired', 30_000).catch(() => { });
147
148
  await this.bridge.send('Page.navigate', { url });
148
149
  await loadPromise;
150
+ this._lastUrl = url;
149
151
  if (options?.waitUntil !== 'none') {
150
152
  const maxMs = options?.settleMs ?? 1000;
151
153
  await this.evaluate(waitForDomStableJs(maxMs, Math.min(500, maxMs)));
@@ -251,6 +253,9 @@ class CDPPage {
251
253
  async consoleMessages(_level) {
252
254
  return [];
253
255
  }
256
+ async getCurrentUrl() {
257
+ return this._lastUrl;
258
+ }
254
259
  async installInterceptor(pattern) {
255
260
  const { generateInterceptorJs } = await import('../interceptor.js');
256
261
  await this.evaluate(generateInterceptorJs(JSON.stringify(pattern), {
@@ -15,13 +15,17 @@ export async function checkDaemonStatus(opts) {
15
15
  const port = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
16
16
  const controller = new AbortController();
17
17
  const timer = setTimeout(() => controller.abort(), opts?.timeout ?? 2000);
18
- const res = await fetch(`http://127.0.0.1:${port}/status`, {
19
- headers: { 'X-OpenCLI': '1' },
20
- signal: controller.signal,
21
- });
22
- const data = await res.json();
23
- clearTimeout(timer);
24
- return { running: true, extensionConnected: data.extensionConnected, extensionVersion: data.extensionVersion };
18
+ try {
19
+ const res = await fetch(`http://127.0.0.1:${port}/status`, {
20
+ headers: { 'X-OpenCLI': '1' },
21
+ signal: controller.signal,
22
+ });
23
+ const data = await res.json();
24
+ return { running: true, extensionConnected: data.extensionConnected, extensionVersion: data.extensionVersion };
25
+ }
26
+ finally {
27
+ clearTimeout(timer);
28
+ }
25
29
  }
26
30
  catch {
27
31
  return { running: false, extensionConnected: false };
@@ -12,6 +12,7 @@ export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapsho
12
12
  export { generateStealthJs } from './stealth.js';
13
13
  export type { DomSnapshotOptions } from './dom-snapshot.js';
14
14
  import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
15
+ import { isRetryableSettleError } from './page.js';
15
16
  import { withTimeoutMs } from '../runtime.js';
16
17
  export declare const __test__: {
17
18
  extractTabEntries: typeof extractTabEntries;
@@ -20,4 +21,5 @@ export declare const __test__: {
20
21
  withTimeoutMs: typeof withTimeoutMs;
21
22
  selectCDPTarget: (targets: import("./cdp.js").CDPTarget[]) => import("./cdp.js").CDPTarget | undefined;
22
23
  scoreCDPTarget: (target: import("./cdp.js").CDPTarget, preferredPattern?: RegExp) => number;
24
+ isRetryableSettleError: typeof isRetryableSettleError;
23
25
  };
@@ -12,6 +12,7 @@ export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapsho
12
12
  export { generateStealthJs } from './stealth.js';
13
13
  import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
14
14
  import { __test__ as cdpTest } from './cdp.js';
15
+ import { isRetryableSettleError } from './page.js';
15
16
  import { withTimeoutMs } from '../runtime.js';
16
17
  export const __test__ = {
17
18
  extractTabEntries,
@@ -20,4 +21,5 @@ export const __test__ = {
20
21
  withTimeoutMs,
21
22
  selectCDPTarget: cdpTest.selectCDPTarget,
22
23
  scoreCDPTarget: cdpTest.scoreCDPTarget,
24
+ isRetryableSettleError,
23
25
  };
@@ -10,6 +10,7 @@
10
10
  * chrome-extension:// tab that can't be debugged.
11
11
  */
12
12
  import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
13
+ export declare function isRetryableSettleError(err: unknown): boolean;
13
14
  /**
14
15
  * Page — implements IPage by talking to the daemon via HTTP.
15
16
  */
@@ -18,6 +19,8 @@ export declare class Page implements IPage {
18
19
  constructor(workspace?: string);
19
20
  /** Active tab ID, set after navigate and used in all subsequent commands */
20
21
  private _tabId;
22
+ /** Last navigated URL, tracked in-memory to avoid extra round-trips */
23
+ private _lastUrl;
21
24
  /** Helper: spread workspace into command params */
22
25
  private _wsOpt;
23
26
  /** Helper: spread workspace + tabId into command params */
@@ -26,6 +29,7 @@ export declare class Page implements IPage {
26
29
  waitUntil?: 'load' | 'none';
27
30
  settleMs?: number;
28
31
  }): Promise<void>;
32
+ getCurrentUrl(): Promise<string | null>;
29
33
  /** Close the automation window in the extension */
30
34
  closeWindow(): Promise<void>;
31
35
  evaluate(js: string): Promise<unknown>;
@@ -16,6 +16,11 @@ import { saveBase64ToFile } from '../utils.js';
16
16
  import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
17
17
  import { generateStealthJs } from './stealth.js';
18
18
  import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, scrollJs, autoScrollJs, networkRequestsJs, waitForDomStableJs, } from './dom-helpers.js';
19
+ export function isRetryableSettleError(err) {
20
+ const message = err instanceof Error ? err.message : String(err);
21
+ return message.includes('Inspected target navigated or closed')
22
+ || (message.includes('-32000') && message.toLowerCase().includes('target'));
23
+ }
19
24
  /**
20
25
  * Page — implements IPage by talking to the daemon via HTTP.
21
26
  */
@@ -26,6 +31,8 @@ export class Page {
26
31
  }
27
32
  /** Active tab ID, set after navigate and used in all subsequent commands */
28
33
  _tabId;
34
+ /** Last navigated URL, tracked in-memory to avoid extra round-trips */
35
+ _lastUrl = null;
29
36
  /** Helper: spread workspace into command params */
30
37
  _wsOpt() {
31
38
  return { workspace: this.workspace };
@@ -42,10 +49,11 @@ export class Page {
42
49
  url,
43
50
  ...this._cmdOpts(),
44
51
  });
45
- // Remember the tabId for subsequent exec calls
52
+ // Remember the tabId and URL for subsequent calls
46
53
  if (result?.tabId) {
47
54
  this._tabId = result.tabId;
48
55
  }
56
+ this._lastUrl = url;
49
57
  // Inject stealth anti-detection patches (guard flag prevents double-injection).
50
58
  try {
51
59
  await sendCommand('exec', {
@@ -60,12 +68,36 @@ export class Page {
60
68
  // settleMs is now a timeout cap (default 1000ms), not a fixed wait.
61
69
  if (options?.waitUntil !== 'none') {
62
70
  const maxMs = options?.settleMs ?? 1000;
63
- await sendCommand('exec', {
71
+ const settleOpts = {
64
72
  code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
65
73
  ...this._cmdOpts(),
66
- });
74
+ };
75
+ try {
76
+ await sendCommand('exec', settleOpts);
77
+ }
78
+ catch (err) {
79
+ if (!isRetryableSettleError(err))
80
+ throw err;
81
+ // SPA client-side redirects can invalidate the CDP target after
82
+ // chrome.tabs reports 'complete'. Wait briefly for the new document
83
+ // to load, then retry the settle probe once.
84
+ try {
85
+ await new Promise((r) => setTimeout(r, 200));
86
+ await sendCommand('exec', settleOpts);
87
+ }
88
+ catch (retryErr) {
89
+ if (!isRetryableSettleError(retryErr))
90
+ throw retryErr;
91
+ // Retry also failed — give up silently. Settle is best-effort
92
+ // after successful navigation; the next real command will surface
93
+ // any persistent target error immediately.
94
+ }
95
+ }
67
96
  }
68
97
  }
98
+ async getCurrentUrl() {
99
+ return this._lastUrl;
100
+ }
69
101
  /** Close the automation window in the extension */
70
102
  async closeWindow() {
71
103
  try {
@@ -163,6 +195,23 @@ export class Page {
163
195
  }
164
196
  async wait(options) {
165
197
  if (typeof options === 'number') {
198
+ if (options >= 1) {
199
+ // For waits >= 1s, use DOM-stable check: return early when the page
200
+ // stops mutating, with the original wait time as the hard cap.
201
+ // This turns e.g. `page.wait(5)` from a fixed 5s sleep into
202
+ // "wait until DOM is stable, max 5s" — often completing in <1s.
203
+ try {
204
+ const maxMs = options * 1000;
205
+ await sendCommand('exec', {
206
+ code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
207
+ ...this._cmdOpts(),
208
+ });
209
+ return;
210
+ }
211
+ catch {
212
+ // Fallback: fixed sleep (e.g. if page has no DOM yet)
213
+ }
214
+ }
166
215
  await new Promise(resolve => setTimeout(resolve, options * 1000));
167
216
  return;
168
217
  }
@@ -31,6 +31,11 @@ describe('browser helpers', () => {
31
31
  it('times out slow promises', async () => {
32
32
  await expect(__test__.withTimeoutMs(new Promise(() => { }), 10, 'timeout')).rejects.toThrow('timeout');
33
33
  });
34
+ it('retries settle only for target-invalidated errors', () => {
35
+ expect(__test__.isRetryableSettleError(new Error('{"code":-32000,"message":"Inspected target navigated or closed"}'))).toBe(true);
36
+ expect(__test__.isRetryableSettleError(new Error('attach failed: target no longer exists'))).toBe(false);
37
+ expect(__test__.isRetryableSettleError(new Error('malformed exec payload'))).toBe(false);
38
+ });
34
39
  it('prefers the real Electron app target over DevTools and blank pages', () => {
35
40
  const target = __test__.selectCDPTarget([
36
41
  {
@@ -1311,6 +1311,464 @@
1311
1311
  "mediaLinks"
1312
1312
  ]
1313
1313
  },
1314
+ {
1315
+ "site": "bluesky",
1316
+ "name": "feeds",
1317
+ "description": "Popular Bluesky feed generators",
1318
+ "domain": "public.api.bsky.app",
1319
+ "strategy": "public",
1320
+ "browser": false,
1321
+ "args": [
1322
+ {
1323
+ "name": "limit",
1324
+ "type": "int",
1325
+ "default": 20,
1326
+ "required": false,
1327
+ "positional": false,
1328
+ "help": "Number of feeds"
1329
+ }
1330
+ ],
1331
+ "columns": [
1332
+ "rank",
1333
+ "name",
1334
+ "likes",
1335
+ "creator",
1336
+ "description"
1337
+ ],
1338
+ "pipeline": [
1339
+ {
1340
+ "fetch": {
1341
+ "url": "https://public.api.bsky.app/xrpc/app.bsky.unspecced.getPopularFeedGenerators?limit=${{ args.limit }}"
1342
+ }
1343
+ },
1344
+ {
1345
+ "select": "feeds"
1346
+ },
1347
+ {
1348
+ "map": {
1349
+ "rank": "${{ index + 1 }}",
1350
+ "name": "${{ item.displayName }}",
1351
+ "likes": "${{ item.likeCount }}",
1352
+ "creator": "${{ item.creator.handle }}",
1353
+ "description": "${{ item.description }}"
1354
+ }
1355
+ },
1356
+ {
1357
+ "limit": "${{ args.limit }}"
1358
+ }
1359
+ ],
1360
+ "type": "yaml"
1361
+ },
1362
+ {
1363
+ "site": "bluesky",
1364
+ "name": "followers",
1365
+ "description": "List followers of a Bluesky user",
1366
+ "domain": "public.api.bsky.app",
1367
+ "strategy": "public",
1368
+ "browser": false,
1369
+ "args": [
1370
+ {
1371
+ "name": "handle",
1372
+ "type": "str",
1373
+ "required": true,
1374
+ "positional": true,
1375
+ "help": "Bluesky handle"
1376
+ },
1377
+ {
1378
+ "name": "limit",
1379
+ "type": "int",
1380
+ "default": 20,
1381
+ "required": false,
1382
+ "positional": false,
1383
+ "help": "Number of followers"
1384
+ }
1385
+ ],
1386
+ "columns": [
1387
+ "rank",
1388
+ "handle",
1389
+ "name",
1390
+ "description"
1391
+ ],
1392
+ "pipeline": [
1393
+ {
1394
+ "fetch": {
1395
+ "url": "https://public.api.bsky.app/xrpc/app.bsky.graph.getFollowers?actor=${{ args.handle }}&limit=${{ args.limit }}"
1396
+ }
1397
+ },
1398
+ {
1399
+ "select": "followers"
1400
+ },
1401
+ {
1402
+ "map": {
1403
+ "rank": "${{ index + 1 }}",
1404
+ "handle": "${{ item.handle }}",
1405
+ "name": "${{ item.displayName }}",
1406
+ "description": "${{ item.description }}"
1407
+ }
1408
+ },
1409
+ {
1410
+ "limit": "${{ args.limit }}"
1411
+ }
1412
+ ],
1413
+ "type": "yaml"
1414
+ },
1415
+ {
1416
+ "site": "bluesky",
1417
+ "name": "following",
1418
+ "description": "List accounts a Bluesky user is following",
1419
+ "domain": "public.api.bsky.app",
1420
+ "strategy": "public",
1421
+ "browser": false,
1422
+ "args": [
1423
+ {
1424
+ "name": "handle",
1425
+ "type": "str",
1426
+ "required": true,
1427
+ "positional": true,
1428
+ "help": "Bluesky handle"
1429
+ },
1430
+ {
1431
+ "name": "limit",
1432
+ "type": "int",
1433
+ "default": 20,
1434
+ "required": false,
1435
+ "positional": false,
1436
+ "help": "Number of accounts"
1437
+ }
1438
+ ],
1439
+ "columns": [
1440
+ "rank",
1441
+ "handle",
1442
+ "name",
1443
+ "description"
1444
+ ],
1445
+ "pipeline": [
1446
+ {
1447
+ "fetch": {
1448
+ "url": "https://public.api.bsky.app/xrpc/app.bsky.graph.getFollows?actor=${{ args.handle }}&limit=${{ args.limit }}"
1449
+ }
1450
+ },
1451
+ {
1452
+ "select": "follows"
1453
+ },
1454
+ {
1455
+ "map": {
1456
+ "rank": "${{ index + 1 }}",
1457
+ "handle": "${{ item.handle }}",
1458
+ "name": "${{ item.displayName }}",
1459
+ "description": "${{ item.description }}"
1460
+ }
1461
+ },
1462
+ {
1463
+ "limit": "${{ args.limit }}"
1464
+ }
1465
+ ],
1466
+ "type": "yaml"
1467
+ },
1468
+ {
1469
+ "site": "bluesky",
1470
+ "name": "profile",
1471
+ "description": "Get Bluesky user profile info",
1472
+ "domain": "public.api.bsky.app",
1473
+ "strategy": "public",
1474
+ "browser": false,
1475
+ "args": [
1476
+ {
1477
+ "name": "handle",
1478
+ "type": "str",
1479
+ "required": true,
1480
+ "positional": true,
1481
+ "help": "Bluesky handle (e.g. bsky.app, jay.bsky.team)"
1482
+ }
1483
+ ],
1484
+ "columns": [
1485
+ "handle",
1486
+ "name",
1487
+ "followers",
1488
+ "following",
1489
+ "posts",
1490
+ "description"
1491
+ ],
1492
+ "pipeline": [
1493
+ {
1494
+ "fetch": {
1495
+ "url": "https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${{ args.handle }}"
1496
+ }
1497
+ },
1498
+ {
1499
+ "map": {
1500
+ "handle": "${{ item.handle }}",
1501
+ "name": "${{ item.displayName }}",
1502
+ "followers": "${{ item.followersCount }}",
1503
+ "following": "${{ item.followsCount }}",
1504
+ "posts": "${{ item.postsCount }}",
1505
+ "description": "${{ item.description }}"
1506
+ }
1507
+ }
1508
+ ],
1509
+ "type": "yaml"
1510
+ },
1511
+ {
1512
+ "site": "bluesky",
1513
+ "name": "search",
1514
+ "description": "Search Bluesky users",
1515
+ "domain": "public.api.bsky.app",
1516
+ "strategy": "public",
1517
+ "browser": false,
1518
+ "args": [
1519
+ {
1520
+ "name": "query",
1521
+ "type": "str",
1522
+ "required": true,
1523
+ "positional": true,
1524
+ "help": "Search query"
1525
+ },
1526
+ {
1527
+ "name": "limit",
1528
+ "type": "int",
1529
+ "default": 10,
1530
+ "required": false,
1531
+ "positional": false,
1532
+ "help": "Number of results"
1533
+ }
1534
+ ],
1535
+ "columns": [
1536
+ "rank",
1537
+ "handle",
1538
+ "name",
1539
+ "followers",
1540
+ "description"
1541
+ ],
1542
+ "pipeline": [
1543
+ {
1544
+ "fetch": {
1545
+ "url": "https://public.api.bsky.app/xrpc/app.bsky.actor.searchActors?q=${{ args.query }}&limit=${{ args.limit }}"
1546
+ }
1547
+ },
1548
+ {
1549
+ "select": "actors"
1550
+ },
1551
+ {
1552
+ "map": {
1553
+ "rank": "${{ index + 1 }}",
1554
+ "handle": "${{ item.handle }}",
1555
+ "name": "${{ item.displayName }}",
1556
+ "followers": "${{ item.followersCount }}",
1557
+ "description": "${{ item.description }}"
1558
+ }
1559
+ },
1560
+ {
1561
+ "limit": "${{ args.limit }}"
1562
+ }
1563
+ ],
1564
+ "type": "yaml"
1565
+ },
1566
+ {
1567
+ "site": "bluesky",
1568
+ "name": "starter-packs",
1569
+ "description": "Get starter packs created by a Bluesky user",
1570
+ "domain": "public.api.bsky.app",
1571
+ "strategy": "public",
1572
+ "browser": false,
1573
+ "args": [
1574
+ {
1575
+ "name": "handle",
1576
+ "type": "str",
1577
+ "required": true,
1578
+ "positional": true,
1579
+ "help": "Bluesky handle"
1580
+ },
1581
+ {
1582
+ "name": "limit",
1583
+ "type": "int",
1584
+ "default": 10,
1585
+ "required": false,
1586
+ "positional": false,
1587
+ "help": "Number of starter packs"
1588
+ }
1589
+ ],
1590
+ "columns": [
1591
+ "rank",
1592
+ "name",
1593
+ "description",
1594
+ "members",
1595
+ "joins"
1596
+ ],
1597
+ "pipeline": [
1598
+ {
1599
+ "fetch": {
1600
+ "url": "https://public.api.bsky.app/xrpc/app.bsky.graph.getActorStarterPacks?actor=${{ args.handle }}&limit=${{ args.limit }}"
1601
+ }
1602
+ },
1603
+ {
1604
+ "select": "starterPacks"
1605
+ },
1606
+ {
1607
+ "map": {
1608
+ "rank": "${{ index + 1 }}",
1609
+ "name": "${{ item.record.name }}",
1610
+ "description": "${{ item.record.description }}",
1611
+ "members": "${{ item.listItemCount }}",
1612
+ "joins": "${{ item.joinedAllTimeCount }}"
1613
+ }
1614
+ },
1615
+ {
1616
+ "limit": "${{ args.limit }}"
1617
+ }
1618
+ ],
1619
+ "type": "yaml"
1620
+ },
1621
+ {
1622
+ "site": "bluesky",
1623
+ "name": "thread",
1624
+ "description": "Get a Bluesky post thread with replies",
1625
+ "domain": "public.api.bsky.app",
1626
+ "strategy": "public",
1627
+ "browser": false,
1628
+ "args": [
1629
+ {
1630
+ "name": "uri",
1631
+ "type": "str",
1632
+ "required": true,
1633
+ "positional": true,
1634
+ "help": "Post AT URI (at://did:.../app.bsky.feed.post/...) or bsky.app URL"
1635
+ },
1636
+ {
1637
+ "name": "limit",
1638
+ "type": "int",
1639
+ "default": 20,
1640
+ "required": false,
1641
+ "positional": false,
1642
+ "help": "Number of replies"
1643
+ }
1644
+ ],
1645
+ "columns": [
1646
+ "author",
1647
+ "text",
1648
+ "likes",
1649
+ "reposts",
1650
+ "replies_count"
1651
+ ],
1652
+ "pipeline": [
1653
+ {
1654
+ "fetch": {
1655
+ "url": "https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread?uri=${{ args.uri }}&depth=2"
1656
+ }
1657
+ },
1658
+ {
1659
+ "select": "thread"
1660
+ },
1661
+ {
1662
+ "map": {
1663
+ "author": "${{ item.post.author.handle }}",
1664
+ "text": "${{ item.post.record.text }}",
1665
+ "likes": "${{ item.post.likeCount }}",
1666
+ "reposts": "${{ item.post.repostCount }}",
1667
+ "replies_count": "${{ item.post.replyCount }}"
1668
+ }
1669
+ }
1670
+ ],
1671
+ "type": "yaml"
1672
+ },
1673
+ {
1674
+ "site": "bluesky",
1675
+ "name": "trending",
1676
+ "description": "Trending topics on Bluesky",
1677
+ "domain": "public.api.bsky.app",
1678
+ "strategy": "public",
1679
+ "browser": false,
1680
+ "args": [
1681
+ {
1682
+ "name": "limit",
1683
+ "type": "int",
1684
+ "default": 20,
1685
+ "required": false,
1686
+ "positional": false,
1687
+ "help": "Number of topics"
1688
+ }
1689
+ ],
1690
+ "columns": [
1691
+ "rank",
1692
+ "topic",
1693
+ "link"
1694
+ ],
1695
+ "pipeline": [
1696
+ {
1697
+ "fetch": {
1698
+ "url": "https://public.api.bsky.app/xrpc/app.bsky.unspecced.getTrendingTopics"
1699
+ }
1700
+ },
1701
+ {
1702
+ "select": "topics"
1703
+ },
1704
+ {
1705
+ "map": {
1706
+ "rank": "${{ index + 1 }}",
1707
+ "topic": "${{ item.topic }}",
1708
+ "link": "${{ item.link }}"
1709
+ }
1710
+ },
1711
+ {
1712
+ "limit": "${{ args.limit }}"
1713
+ }
1714
+ ],
1715
+ "type": "yaml"
1716
+ },
1717
+ {
1718
+ "site": "bluesky",
1719
+ "name": "user",
1720
+ "description": "Get recent posts from a Bluesky user",
1721
+ "domain": "public.api.bsky.app",
1722
+ "strategy": "public",
1723
+ "browser": false,
1724
+ "args": [
1725
+ {
1726
+ "name": "handle",
1727
+ "type": "str",
1728
+ "required": true,
1729
+ "positional": true,
1730
+ "help": "Bluesky handle (e.g. bsky.app)"
1731
+ },
1732
+ {
1733
+ "name": "limit",
1734
+ "type": "int",
1735
+ "default": 20,
1736
+ "required": false,
1737
+ "positional": false,
1738
+ "help": "Number of posts"
1739
+ }
1740
+ ],
1741
+ "columns": [
1742
+ "rank",
1743
+ "text",
1744
+ "likes",
1745
+ "reposts",
1746
+ "replies"
1747
+ ],
1748
+ "pipeline": [
1749
+ {
1750
+ "fetch": {
1751
+ "url": "https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${{ args.handle }}&limit=${{ args.limit }}"
1752
+ }
1753
+ },
1754
+ {
1755
+ "select": "feed"
1756
+ },
1757
+ {
1758
+ "map": {
1759
+ "rank": "${{ index + 1 }}",
1760
+ "text": "${{ item.post.record.text }}",
1761
+ "likes": "${{ item.post.likeCount }}",
1762
+ "reposts": "${{ item.post.repostCount }}",
1763
+ "replies": "${{ item.post.replyCount }}"
1764
+ }
1765
+ },
1766
+ {
1767
+ "limit": "${{ args.limit }}"
1768
+ }
1769
+ ],
1770
+ "type": "yaml"
1771
+ },
1314
1772
  {
1315
1773
  "site": "boss",
1316
1774
  "name": "batchgreet",
@@ -11801,7 +12259,7 @@
11801
12259
  {
11802
12260
  "name": "images",
11803
12261
  "type": "str",
11804
- "required": false,
12262
+ "required": true,
11805
12263
  "help": "图片路径,逗号分隔,最多9张 (jpg/png/gif/webp)"
11806
12264
  },
11807
12265
  {
@@ -11856,6 +12314,7 @@
11856
12314
  "title",
11857
12315
  "author",
11858
12316
  "likes",
12317
+ "published_at",
11859
12318
  "url"
11860
12319
  ]
11861
12320
  },