@jackwener/opencli 1.5.0 → 1.5.1

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 (79) hide show
  1. package/dist/browser/cdp.js +5 -0
  2. package/dist/browser/page.d.ts +3 -0
  3. package/dist/browser/page.js +24 -1
  4. package/dist/cli-manifest.json +465 -5
  5. package/dist/cli.js +34 -3
  6. package/dist/clis/bluesky/feeds.yaml +29 -0
  7. package/dist/clis/bluesky/followers.yaml +33 -0
  8. package/dist/clis/bluesky/following.yaml +33 -0
  9. package/dist/clis/bluesky/profile.yaml +27 -0
  10. package/dist/clis/bluesky/search.yaml +34 -0
  11. package/dist/clis/bluesky/starter-packs.yaml +34 -0
  12. package/dist/clis/bluesky/thread.yaml +32 -0
  13. package/dist/clis/bluesky/trending.yaml +27 -0
  14. package/dist/clis/bluesky/user.yaml +34 -0
  15. package/dist/clis/twitter/trending.js +29 -61
  16. package/dist/clis/v2ex/hot.yaml +17 -3
  17. package/dist/clis/xiaohongshu/publish.js +78 -42
  18. package/dist/clis/xiaohongshu/publish.test.js +20 -8
  19. package/dist/clis/xiaohongshu/search.d.ts +8 -1
  20. package/dist/clis/xiaohongshu/search.js +20 -1
  21. package/dist/clis/xiaohongshu/search.test.d.ts +1 -1
  22. package/dist/clis/xiaohongshu/search.test.js +32 -1
  23. package/dist/discovery.js +40 -28
  24. package/dist/doctor.d.ts +1 -2
  25. package/dist/doctor.js +2 -2
  26. package/dist/engine.test.js +42 -0
  27. package/dist/errors.d.ts +1 -1
  28. package/dist/errors.js +2 -2
  29. package/dist/execution.js +45 -7
  30. package/dist/execution.test.d.ts +1 -0
  31. package/dist/execution.test.js +40 -0
  32. package/dist/external.js +6 -1
  33. package/dist/main.js +1 -0
  34. package/dist/plugin-scaffold.d.ts +28 -0
  35. package/dist/plugin-scaffold.js +142 -0
  36. package/dist/plugin-scaffold.test.d.ts +4 -0
  37. package/dist/plugin-scaffold.test.js +83 -0
  38. package/dist/plugin.d.ts +55 -17
  39. package/dist/plugin.js +706 -154
  40. package/dist/plugin.test.js +836 -38
  41. package/dist/runtime.d.ts +1 -0
  42. package/dist/runtime.js +1 -1
  43. package/dist/types.d.ts +2 -0
  44. package/docs/adapters/browser/bluesky.md +53 -0
  45. package/docs/guide/plugins.md +10 -0
  46. package/package.json +1 -1
  47. package/src/browser/cdp.ts +6 -0
  48. package/src/browser/page.ts +24 -1
  49. package/src/cli.ts +34 -3
  50. package/src/clis/bluesky/feeds.yaml +29 -0
  51. package/src/clis/bluesky/followers.yaml +33 -0
  52. package/src/clis/bluesky/following.yaml +33 -0
  53. package/src/clis/bluesky/profile.yaml +27 -0
  54. package/src/clis/bluesky/search.yaml +34 -0
  55. package/src/clis/bluesky/starter-packs.yaml +34 -0
  56. package/src/clis/bluesky/thread.yaml +32 -0
  57. package/src/clis/bluesky/trending.yaml +27 -0
  58. package/src/clis/bluesky/user.yaml +34 -0
  59. package/src/clis/twitter/trending.ts +29 -77
  60. package/src/clis/v2ex/hot.yaml +17 -3
  61. package/src/clis/xiaohongshu/publish.test.ts +22 -8
  62. package/src/clis/xiaohongshu/publish.ts +93 -52
  63. package/src/clis/xiaohongshu/search.test.ts +39 -1
  64. package/src/clis/xiaohongshu/search.ts +19 -1
  65. package/src/discovery.ts +41 -33
  66. package/src/doctor.ts +2 -3
  67. package/src/engine.test.ts +38 -0
  68. package/src/errors.ts +6 -2
  69. package/src/execution.test.ts +47 -0
  70. package/src/execution.ts +39 -6
  71. package/src/external.ts +6 -1
  72. package/src/main.ts +1 -0
  73. package/src/plugin-scaffold.test.ts +98 -0
  74. package/src/plugin-scaffold.ts +170 -0
  75. package/src/plugin.test.ts +881 -38
  76. package/src/plugin.ts +871 -158
  77. package/src/runtime.ts +2 -2
  78. package/src/types.ts +2 -0
  79. package/tests/e2e/browser-public.test.ts +1 -1
@@ -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), {
@@ -18,6 +18,8 @@ export declare class Page implements IPage {
18
18
  constructor(workspace?: string);
19
19
  /** Active tab ID, set after navigate and used in all subsequent commands */
20
20
  private _tabId;
21
+ /** Last navigated URL, tracked in-memory to avoid extra round-trips */
22
+ private _lastUrl;
21
23
  /** Helper: spread workspace into command params */
22
24
  private _wsOpt;
23
25
  /** Helper: spread workspace + tabId into command params */
@@ -26,6 +28,7 @@ export declare class Page implements IPage {
26
28
  waitUntil?: 'load' | 'none';
27
29
  settleMs?: number;
28
30
  }): Promise<void>;
31
+ getCurrentUrl(): Promise<string | null>;
29
32
  /** Close the automation window in the extension */
30
33
  closeWindow(): Promise<void>;
31
34
  evaluate(js: string): Promise<unknown>;
@@ -26,6 +26,8 @@ export class Page {
26
26
  }
27
27
  /** Active tab ID, set after navigate and used in all subsequent commands */
28
28
  _tabId;
29
+ /** Last navigated URL, tracked in-memory to avoid extra round-trips */
30
+ _lastUrl = null;
29
31
  /** Helper: spread workspace into command params */
30
32
  _wsOpt() {
31
33
  return { workspace: this.workspace };
@@ -42,10 +44,11 @@ export class Page {
42
44
  url,
43
45
  ...this._cmdOpts(),
44
46
  });
45
- // Remember the tabId for subsequent exec calls
47
+ // Remember the tabId and URL for subsequent calls
46
48
  if (result?.tabId) {
47
49
  this._tabId = result.tabId;
48
50
  }
51
+ this._lastUrl = url;
49
52
  // Inject stealth anti-detection patches (guard flag prevents double-injection).
50
53
  try {
51
54
  await sendCommand('exec', {
@@ -66,6 +69,9 @@ export class Page {
66
69
  });
67
70
  }
68
71
  }
72
+ async getCurrentUrl() {
73
+ return this._lastUrl;
74
+ }
69
75
  /** Close the automation window in the extension */
70
76
  async closeWindow() {
71
77
  try {
@@ -163,6 +169,23 @@ export class Page {
163
169
  }
164
170
  async wait(options) {
165
171
  if (typeof options === 'number') {
172
+ if (options >= 1) {
173
+ // For waits >= 1s, use DOM-stable check: return early when the page
174
+ // stops mutating, with the original wait time as the hard cap.
175
+ // This turns e.g. `page.wait(5)` from a fixed 5s sleep into
176
+ // "wait until DOM is stable, max 5s" — often completing in <1s.
177
+ try {
178
+ const maxMs = options * 1000;
179
+ await sendCommand('exec', {
180
+ code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
181
+ ...this._cmdOpts(),
182
+ });
183
+ return;
184
+ }
185
+ catch {
186
+ // Fallback: fixed sleep (e.g. if page has no DOM yet)
187
+ }
188
+ }
166
189
  await new Promise(resolve => setTimeout(resolve, options * 1000));
167
190
  return;
168
191
  }
@@ -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",
@@ -10442,7 +10900,7 @@
10442
10900
  "description": "V2EX 热门话题",
10443
10901
  "domain": "www.v2ex.com",
10444
10902
  "strategy": "public",
10445
- "browser": false,
10903
+ "browser": true,
10446
10904
  "args": [
10447
10905
  {
10448
10906
  "name": "limit",
@@ -10460,9 +10918,10 @@
10460
10918
  ],
10461
10919
  "pipeline": [
10462
10920
  {
10463
- "fetch": {
10464
- "url": "https://www.v2ex.com/api/topics/hot.json"
10465
- }
10921
+ "navigate": "https://www.v2ex.com/"
10922
+ },
10923
+ {
10924
+ "evaluate": "(async () => {\n const response = await fetch('/api/topics/hot.json', {\n credentials: 'include',\n headers: {\n accept: 'application/json, text/plain, */*',\n 'x-requested-with': 'XMLHttpRequest',\n },\n });\n if (!response.ok) {\n throw new Error(`V2EX hot API request failed: ${response.status}`);\n }\n return await response.json();\n})()\n"
10466
10925
  },
10467
10926
  {
10468
10927
  "map": {
@@ -11801,7 +12260,7 @@
11801
12260
  {
11802
12261
  "name": "images",
11803
12262
  "type": "str",
11804
- "required": false,
12263
+ "required": true,
11805
12264
  "help": "图片路径,逗号分隔,最多9张 (jpg/png/gif/webp)"
11806
12265
  },
11807
12266
  {
@@ -11856,6 +12315,7 @@
11856
12315
  "title",
11857
12316
  "author",
11858
12317
  "likes",
12318
+ "published_at",
11859
12319
  "url"
11860
12320
  ]
11861
12321
  },
package/dist/cli.js CHANGED
@@ -69,9 +69,10 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
69
69
  for (const [site, cmds] of sites) {
70
70
  console.log(chalk.bold.cyan(` ${site}`));
71
71
  for (const cmd of cmds) {
72
- const tag = strategyLabel(cmd) === 'public'
72
+ const label = strategyLabel(cmd);
73
+ const tag = label === 'public'
73
74
  ? chalk.green('[public]')
74
- : chalk.yellow(`[${strategyLabel(cmd)}]`);
75
+ : chalk.yellow(`[${label}]`);
75
76
  console.log(` ${cmd.name} ${tag}${cmd.description ? chalk.dim(` — ${cmd.description}`) : ''}`);
76
77
  }
77
78
  console.log();
@@ -228,7 +229,7 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
228
229
  const pluginCmd = program.command('plugin').description('Manage opencli plugins');
229
230
  pluginCmd
230
231
  .command('install')
231
- .description('Install a plugin from GitHub')
232
+ .description('Install a plugin from a git repository')
232
233
  .argument('<source>', 'Plugin source (e.g. github:user/repo)')
233
234
  .action(async (source) => {
234
235
  const { installPlugin } = await import('./plugin.js');
@@ -380,6 +381,36 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
380
381
  console.log(chalk.dim(` ${plugins.length} plugin(s) installed`));
381
382
  console.log();
382
383
  });
384
+ pluginCmd
385
+ .command('create')
386
+ .description('Create a new plugin scaffold')
387
+ .argument('<name>', 'Plugin name (lowercase, hyphens allowed)')
388
+ .option('-d, --dir <path>', 'Output directory (default: ./<name>)')
389
+ .option('--description <text>', 'Plugin description')
390
+ .action(async (name, opts) => {
391
+ const { createPluginScaffold } = await import('./plugin-scaffold.js');
392
+ try {
393
+ const result = createPluginScaffold(name, {
394
+ dir: opts.dir,
395
+ description: opts.description,
396
+ });
397
+ console.log(chalk.green(`✅ Plugin scaffold created at ${result.dir}`));
398
+ console.log();
399
+ console.log(chalk.bold(' Files created:'));
400
+ for (const f of result.files) {
401
+ console.log(` ${chalk.cyan(f)}`);
402
+ }
403
+ console.log();
404
+ console.log(chalk.dim(' Next steps:'));
405
+ console.log(chalk.dim(` cd ${result.dir}`));
406
+ console.log(chalk.dim(` opencli plugin install file://${result.dir}`));
407
+ console.log(chalk.dim(` opencli ${name} hello`));
408
+ }
409
+ catch (err) {
410
+ console.error(chalk.red(`Error: ${getErrorMessage(err)}`));
411
+ process.exitCode = 1;
412
+ }
413
+ });
383
414
  // ── External CLIs ─────────────────────────────────────────────────────────
384
415
  const externalClis = loadExternalClis();
385
416
  program
@@ -0,0 +1,29 @@
1
+ site: bluesky
2
+ name: feeds
3
+ description: Popular Bluesky feed generators
4
+ domain: public.api.bsky.app
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of feeds
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://public.api.bsky.app/xrpc/app.bsky.unspecced.getPopularFeedGenerators?limit=${{ args.limit }}
17
+
18
+ - select: feeds
19
+
20
+ - map:
21
+ rank: ${{ index + 1 }}
22
+ name: ${{ item.displayName }}
23
+ likes: ${{ item.likeCount }}
24
+ creator: ${{ item.creator.handle }}
25
+ description: ${{ item.description }}
26
+
27
+ - limit: ${{ args.limit }}
28
+
29
+ columns: [rank, name, likes, creator, description]