sophhub 0.2.3 → 0.3.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 (117) hide show
  1. package/package.json +1 -1
  2. package/skills/consensus/skill.json +20 -0
  3. package/skills/consensus/src/SKILL.md +93 -0
  4. package/skills/deepwiki/skill.json +20 -0
  5. package/skills/deepwiki/src/SKILL.md +45 -0
  6. package/skills/deepwiki/src/_meta.json +6 -0
  7. package/skills/deepwiki/src/scripts/deepwiki.js +135 -0
  8. package/skills/feishu-bitable/skill.json +20 -0
  9. package/skills/feishu-bitable/src/CHECKLIST.md +150 -0
  10. package/skills/feishu-bitable/src/README.md +178 -0
  11. package/skills/feishu-bitable/src/SKILL.md +113 -0
  12. package/skills/feishu-bitable/src/_meta.json +6 -0
  13. package/skills/feishu-bitable/src/api.js +381 -0
  14. package/skills/feishu-bitable/src/bin/cli.js +284 -0
  15. package/skills/feishu-bitable/src/description.md +143 -0
  16. package/skills/feishu-bitable/src/examples/create-records.json +52 -0
  17. package/skills/feishu-bitable/src/examples/create-table.json +64 -0
  18. package/skills/feishu-bitable/src/package-lock.json +324 -0
  19. package/skills/feishu-bitable/src/package.json +33 -0
  20. package/skills/feishu-bitable/src/publish-config.json +14 -0
  21. package/skills/feishu-bitable/src/test-simple.js +61 -0
  22. package/skills/feishu-bitable/src/utils.js +261 -0
  23. package/skills/google-maps/skill.json +20 -0
  24. package/skills/google-maps/src/SKILL.md +237 -0
  25. package/skills/google-maps/src/_meta.json +6 -0
  26. package/skills/google-maps/src/lib/map_helper.py +912 -0
  27. package/skills/large-task-router/skill.json +20 -0
  28. package/skills/large-task-router/src/SKILL.md +79 -0
  29. package/skills/large-task-router/src/templates/plan.md +74 -0
  30. package/skills/notes-hub-assistant/skill.json +20 -0
  31. package/skills/notes-hub-assistant/src/SKILL.md +233 -0
  32. package/skills/notes-hub-assistant/src/scripts/_resolve_lark_cli.py +48 -0
  33. package/skills/notes-hub-assistant/src/scripts/openclaw_meeting_minutes.py +473 -0
  34. package/skills/notes-hub-assistant/src/scripts/openclaw_notes_crud.py +596 -0
  35. package/skills/notes-hub-assistant/src/scripts/openclaw_wolai_notes_crud.py +364 -0
  36. package/skills/notes-hub-assistant/src/scripts/run_meeting_minutes.py +79 -0
  37. package/skills/notes-hub-assistant/src/scripts/run_note_crud.py +37 -0
  38. package/skills/notes-hub-assistant/src/scripts/run_notionbot.py +36 -0
  39. package/skills/notes-hub-assistant/src/scripts/run_wolai_note_crud.py +27 -0
  40. package/skills/skillhub/skill.json +11 -4
  41. package/skills/skillhub/src/SKILL.md +11 -1
  42. package/skills/sophnet-dailynews/skill.json +20 -0
  43. package/skills/sophnet-dailynews/src/SKILL.md +179 -0
  44. package/skills/sophnet-dailynews/src/cache.json +151 -0
  45. package/skills/sophnet-dailynews/src/sources.json +230 -0
  46. package/skills/sophnet-schedule/skill.json +20 -0
  47. package/skills/sophnet-schedule/src/ARCHITECTURE.md +321 -0
  48. package/skills/sophnet-schedule/src/IMPROVEMENTS.md +145 -0
  49. package/skills/sophnet-schedule/src/SKILL.md +1050 -0
  50. package/skills/sophnet-schedule/src/_meta.json +6 -0
  51. package/skills/sophnet-schedule/src/api/__init__.py +0 -0
  52. package/skills/sophnet-schedule/src/api/models.py +245 -0
  53. package/skills/sophnet-schedule/src/apps/add_event.py +237 -0
  54. package/skills/sophnet-schedule/src/apps/check_reminders.py +112 -0
  55. package/skills/sophnet-schedule/src/apps/check_roc.py +246 -0
  56. package/skills/sophnet-schedule/src/apps/generate_daily_plan.py +342 -0
  57. package/skills/sophnet-schedule/src/apps/import_events.py +216 -0
  58. package/skills/sophnet-schedule/src/apps/monitor_calendar_changes.py +140 -0
  59. package/skills/sophnet-schedule/src/apps/register_tasks.py +169 -0
  60. package/skills/sophnet-schedule/src/apps/sync_roc_to_gcal.py +174 -0
  61. package/skills/sophnet-schedule/src/compat.py +66 -0
  62. package/skills/sophnet-schedule/src/config/__init__.py +0 -0
  63. package/skills/sophnet-schedule/src/config/reminder_rules.yaml +96 -0
  64. package/skills/sophnet-schedule/src/config/roc_events.yaml +44 -0
  65. package/skills/sophnet-schedule/src/config/settings.py +133 -0
  66. package/skills/sophnet-schedule/src/config/task_registry.yaml +92 -0
  67. package/skills/sophnet-schedule/src/docs/FRONTEND_INTEGRATION_GUIDE.md +437 -0
  68. package/skills/sophnet-schedule/src/gcal/__init__.py +0 -0
  69. package/skills/sophnet-schedule/src/gcal/client.py +374 -0
  70. package/skills/sophnet-schedule/src/gcal/models.py +91 -0
  71. package/skills/sophnet-schedule/src/requirements.txt +6 -0
  72. package/skills/sophnet-schedule/src/scripts/setup_gcal_token.py +85 -0
  73. package/skills/sophnet-schedule/src/server.py +669 -0
  74. package/skills/sophnet-schedule/src/services/__init__.py +0 -0
  75. package/skills/sophnet-schedule/src/services/calendar_backend.py +139 -0
  76. package/skills/sophnet-schedule/src/services/conflict_detector.py +96 -0
  77. package/skills/sophnet-schedule/src/services/datetime_utils.py +117 -0
  78. package/skills/sophnet-schedule/src/services/event_classifier.py +100 -0
  79. package/skills/sophnet-schedule/src/services/event_diff.py +160 -0
  80. package/skills/sophnet-schedule/src/services/google_integration.py +500 -0
  81. package/skills/sophnet-schedule/src/services/job_store.py +100 -0
  82. package/skills/sophnet-schedule/src/services/local_event_store.py +266 -0
  83. package/skills/sophnet-schedule/src/services/reminder_planner.py +116 -0
  84. package/skills/sophnet-schedule/src/services/runtime_utils.py +31 -0
  85. package/skills/sophnet-schedule/src/services/table_parser.py +286 -0
  86. package/skills/sophnet-schedule/src/services/task_builder.py +167 -0
  87. package/skills/sophnet-schedule/src/services/time_window.py +72 -0
  88. package/skills/sophnet-stock/skill.json +20 -0
  89. package/skills/sophnet-stock/src/App-Plan.md +442 -0
  90. package/skills/sophnet-stock/src/README.md +214 -0
  91. package/skills/sophnet-stock/src/SKILL.md +236 -0
  92. package/skills/sophnet-stock/src/TODO.md +394 -0
  93. package/skills/sophnet-stock/src/_meta.json +6 -0
  94. package/skills/sophnet-stock/src/docs/ARCHITECTURE.md +408 -0
  95. package/skills/sophnet-stock/src/docs/CONCEPT.md +233 -0
  96. package/skills/sophnet-stock/src/docs/HOT_SCANNER.md +288 -0
  97. package/skills/sophnet-stock/src/docs/README.md +95 -0
  98. package/skills/sophnet-stock/src/docs/USAGE.md +465 -0
  99. package/skills/sophnet-stock/src/scripts/analyze_stock.py +2565 -0
  100. package/skills/sophnet-stock/src/scripts/dividends.py +365 -0
  101. package/skills/sophnet-stock/src/scripts/hot_scanner.py +582 -0
  102. package/skills/sophnet-stock/src/scripts/portfolio.py +548 -0
  103. package/skills/sophnet-stock/src/scripts/rumor_scanner.py +342 -0
  104. package/skills/sophnet-stock/src/scripts/test_stock_analysis.py +409 -0
  105. package/skills/sophnet-stock/src/scripts/watchlist.py +336 -0
  106. package/skills/xiaohongshu/skill.json +20 -0
  107. package/skills/xiaohongshu/src/SKILL.md +91 -0
  108. package/skills/xiaohongshu/src/_meta.json +6 -0
  109. package/skills/xiaohongshu/src/assets/card.html +216 -0
  110. package/skills/xiaohongshu/src/assets/cover.html +82 -0
  111. package/skills/xiaohongshu/src/assets/example.md +84 -0
  112. package/skills/xiaohongshu/src/assets/styles.css +318 -0
  113. package/skills/xiaohongshu/src/scripts/render_xhs_v2.py +737 -0
  114. package/skills/xiaohongshu/src/scripts/sign_server.py +158 -0
  115. package/skills/xiaohongshu/src/scripts/stealth.min.js +7 -0
  116. package/skills/xiaohongshu/src/scripts/xhs_tool.py +186 -0
  117. package/skills/xiaohongshu/src/workflow.py +185 -0
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.10"
4
+ # dependencies = [
5
+ # "yfinance>=0.2.40",
6
+ # ]
7
+ # ///
8
+ """
9
+ Stock Watchlist with Price Alerts.
10
+
11
+ Usage:
12
+ uv run watchlist.py add AAPL # Add to watchlist
13
+ uv run watchlist.py add AAPL --target 200 # With price target
14
+ uv run watchlist.py add AAPL --stop 150 # With stop loss
15
+ uv run watchlist.py add AAPL --alert-on signal # Alert on signal change
16
+ uv run watchlist.py remove AAPL # Remove from watchlist
17
+ uv run watchlist.py list # Show watchlist
18
+ uv run watchlist.py check # Check for triggered alerts
19
+ uv run watchlist.py check --notify # Check and format for notification
20
+ """
21
+
22
+ import argparse
23
+ import json
24
+ import sys
25
+ from dataclasses import dataclass, asdict
26
+ from datetime import datetime, timezone
27
+ from pathlib import Path
28
+ from typing import Literal
29
+
30
+ import yfinance as yf
31
+
32
+ # Storage
33
+ WATCHLIST_DIR = Path.home() / ".clawdbot" / "skills" / "stock-analysis"
34
+ WATCHLIST_FILE = WATCHLIST_DIR / "watchlist.json"
35
+
36
+
37
+ @dataclass
38
+ class WatchlistItem:
39
+ ticker: str
40
+ added_at: str
41
+ price_at_add: float | None = None
42
+ target_price: float | None = None # Alert when price >= target
43
+ stop_price: float | None = None # Alert when price <= stop
44
+ alert_on_signal: bool = False # Alert when recommendation changes
45
+ last_signal: str | None = None # BUY/HOLD/SELL
46
+ last_check: str | None = None
47
+ notes: str | None = None
48
+
49
+
50
+ @dataclass
51
+ class Alert:
52
+ ticker: str
53
+ alert_type: Literal["target_hit", "stop_hit", "signal_change"]
54
+ message: str
55
+ current_price: float
56
+ trigger_value: float | str
57
+ timestamp: str
58
+
59
+
60
+ def ensure_dirs():
61
+ """Create storage directories."""
62
+ WATCHLIST_DIR.mkdir(parents=True, exist_ok=True)
63
+
64
+
65
+ def load_watchlist() -> list[WatchlistItem]:
66
+ """Load watchlist from file."""
67
+ if WATCHLIST_FILE.exists():
68
+ data = json.loads(WATCHLIST_FILE.read_text())
69
+ return [WatchlistItem(**item) for item in data]
70
+ return []
71
+
72
+
73
+ def save_watchlist(items: list[WatchlistItem]):
74
+ """Save watchlist to file."""
75
+ ensure_dirs()
76
+ data = [asdict(item) for item in items]
77
+ WATCHLIST_FILE.write_text(json.dumps(data, indent=2))
78
+
79
+
80
+ def get_current_price(ticker: str) -> float | None:
81
+ """Get current price for a ticker."""
82
+ try:
83
+ stock = yf.Ticker(ticker)
84
+ price = stock.info.get("regularMarketPrice") or stock.info.get("currentPrice")
85
+ return float(price) if price else None
86
+ except Exception:
87
+ return None
88
+
89
+
90
+ def add_to_watchlist(
91
+ ticker: str,
92
+ target_price: float | None = None,
93
+ stop_price: float | None = None,
94
+ alert_on_signal: bool = False,
95
+ notes: str | None = None,
96
+ ) -> dict:
97
+ """Add ticker to watchlist."""
98
+ ticker = ticker.upper()
99
+
100
+ # Validate ticker
101
+ current_price = get_current_price(ticker)
102
+ if current_price is None:
103
+ return {"success": False, "error": f"Invalid ticker: {ticker}"}
104
+
105
+ # Load existing watchlist
106
+ watchlist = load_watchlist()
107
+
108
+ # Check if already exists
109
+ for item in watchlist:
110
+ if item.ticker == ticker:
111
+ # Update existing
112
+ item.target_price = target_price or item.target_price
113
+ item.stop_price = stop_price or item.stop_price
114
+ item.alert_on_signal = alert_on_signal or item.alert_on_signal
115
+ item.notes = notes or item.notes
116
+ save_watchlist(watchlist)
117
+ return {
118
+ "success": True,
119
+ "action": "updated",
120
+ "ticker": ticker,
121
+ "current_price": current_price,
122
+ "target_price": item.target_price,
123
+ "stop_price": item.stop_price,
124
+ "alert_on_signal": item.alert_on_signal,
125
+ }
126
+
127
+ # Add new
128
+ item = WatchlistItem(
129
+ ticker=ticker,
130
+ added_at=datetime.now(timezone.utc).isoformat(),
131
+ price_at_add=current_price,
132
+ target_price=target_price,
133
+ stop_price=stop_price,
134
+ alert_on_signal=alert_on_signal,
135
+ notes=notes,
136
+ )
137
+ watchlist.append(item)
138
+ save_watchlist(watchlist)
139
+
140
+ return {
141
+ "success": True,
142
+ "action": "added",
143
+ "ticker": ticker,
144
+ "current_price": current_price,
145
+ "target_price": target_price,
146
+ "stop_price": stop_price,
147
+ "alert_on_signal": alert_on_signal,
148
+ }
149
+
150
+
151
+ def remove_from_watchlist(ticker: str) -> dict:
152
+ """Remove ticker from watchlist."""
153
+ ticker = ticker.upper()
154
+ watchlist = load_watchlist()
155
+
156
+ original_len = len(watchlist)
157
+ watchlist = [item for item in watchlist if item.ticker != ticker]
158
+
159
+ if len(watchlist) == original_len:
160
+ return {"success": False, "error": f"{ticker} not in watchlist"}
161
+
162
+ save_watchlist(watchlist)
163
+ return {"success": True, "removed": ticker}
164
+
165
+
166
+ def list_watchlist() -> dict:
167
+ """List all watchlist items with current prices."""
168
+ watchlist = load_watchlist()
169
+
170
+ if not watchlist:
171
+ return {"success": True, "items": [], "count": 0}
172
+
173
+ items = []
174
+ for item in watchlist:
175
+ current_price = get_current_price(item.ticker)
176
+
177
+ # Calculate change since added
178
+ change_pct = None
179
+ if current_price and item.price_at_add:
180
+ change_pct = ((current_price - item.price_at_add) / item.price_at_add) * 100
181
+
182
+ # Distance to target/stop
183
+ to_target = None
184
+ to_stop = None
185
+ if current_price:
186
+ if item.target_price:
187
+ to_target = ((item.target_price - current_price) / current_price) * 100
188
+ if item.stop_price:
189
+ to_stop = ((item.stop_price - current_price) / current_price) * 100
190
+
191
+ items.append({
192
+ "ticker": item.ticker,
193
+ "current_price": current_price,
194
+ "price_at_add": item.price_at_add,
195
+ "change_pct": round(change_pct, 2) if change_pct else None,
196
+ "target_price": item.target_price,
197
+ "to_target_pct": round(to_target, 2) if to_target else None,
198
+ "stop_price": item.stop_price,
199
+ "to_stop_pct": round(to_stop, 2) if to_stop else None,
200
+ "alert_on_signal": item.alert_on_signal,
201
+ "last_signal": item.last_signal,
202
+ "added_at": item.added_at[:10],
203
+ "notes": item.notes,
204
+ })
205
+
206
+ return {"success": True, "items": items, "count": len(items)}
207
+
208
+
209
+ def check_alerts(notify_format: bool = False) -> dict:
210
+ """Check watchlist for triggered alerts."""
211
+ watchlist = load_watchlist()
212
+ alerts: list[Alert] = []
213
+ now = datetime.now(timezone.utc).isoformat()
214
+
215
+ for item in watchlist:
216
+ current_price = get_current_price(item.ticker)
217
+ if current_price is None:
218
+ continue
219
+
220
+ # Check target price
221
+ if item.target_price and current_price >= item.target_price:
222
+ alerts.append(Alert(
223
+ ticker=item.ticker,
224
+ alert_type="target_hit",
225
+ message=f"🎯 {item.ticker} hit target! ${current_price:.2f} >= ${item.target_price:.2f}",
226
+ current_price=current_price,
227
+ trigger_value=item.target_price,
228
+ timestamp=now,
229
+ ))
230
+
231
+ # Check stop price
232
+ if item.stop_price and current_price <= item.stop_price:
233
+ alerts.append(Alert(
234
+ ticker=item.ticker,
235
+ alert_type="stop_hit",
236
+ message=f"🛑 {item.ticker} hit stop! ${current_price:.2f} <= ${item.stop_price:.2f}",
237
+ current_price=current_price,
238
+ trigger_value=item.stop_price,
239
+ timestamp=now,
240
+ ))
241
+
242
+ # Check signal change (requires running analyze_stock)
243
+ if item.alert_on_signal:
244
+ try:
245
+ import subprocess
246
+ result = subprocess.run(
247
+ ["uv", "run", str(Path(__file__).parent / "analyze_stock.py"), item.ticker, "--output", "json"],
248
+ capture_output=True,
249
+ text=True,
250
+ timeout=60,
251
+ )
252
+ if result.returncode == 0:
253
+ analysis = json.loads(result.stdout)
254
+ new_signal = analysis.get("recommendation")
255
+
256
+ if item.last_signal and new_signal and new_signal != item.last_signal:
257
+ alerts.append(Alert(
258
+ ticker=item.ticker,
259
+ alert_type="signal_change",
260
+ message=f"📊 {item.ticker} signal changed: {item.last_signal} → {new_signal}",
261
+ current_price=current_price,
262
+ trigger_value=f"{item.last_signal} → {new_signal}",
263
+ timestamp=now,
264
+ ))
265
+
266
+ # Update last signal
267
+ item.last_signal = new_signal
268
+ except Exception:
269
+ pass
270
+
271
+ item.last_check = now
272
+
273
+ # Save updated watchlist (with last_signal updates)
274
+ save_watchlist(watchlist)
275
+
276
+ # Format output
277
+ if notify_format and alerts:
278
+ # Format for Telegram notification
279
+ lines = ["📢 **Stock Alerts**\n"]
280
+ for alert in alerts:
281
+ lines.append(alert.message)
282
+ return {"success": True, "alerts": [asdict(a) for a in alerts], "notification": "\n".join(lines)}
283
+
284
+ return {"success": True, "alerts": [asdict(a) for a in alerts], "count": len(alerts)}
285
+
286
+
287
+ def main():
288
+ parser = argparse.ArgumentParser(description="Stock Watchlist with Alerts")
289
+ subparsers = parser.add_subparsers(dest="command", required=True)
290
+
291
+ # Add
292
+ add_parser = subparsers.add_parser("add", help="Add ticker to watchlist")
293
+ add_parser.add_argument("ticker", help="Stock ticker")
294
+ add_parser.add_argument("--target", type=float, help="Target price for alert")
295
+ add_parser.add_argument("--stop", type=float, help="Stop loss price for alert")
296
+ add_parser.add_argument("--alert-on", choices=["signal"], help="Alert on signal change")
297
+ add_parser.add_argument("--notes", help="Notes")
298
+
299
+ # Remove
300
+ remove_parser = subparsers.add_parser("remove", help="Remove ticker from watchlist")
301
+ remove_parser.add_argument("ticker", help="Stock ticker")
302
+
303
+ # List
304
+ subparsers.add_parser("list", help="List watchlist")
305
+
306
+ # Check
307
+ check_parser = subparsers.add_parser("check", help="Check for triggered alerts")
308
+ check_parser.add_argument("--notify", action="store_true", help="Format for notification")
309
+
310
+ args = parser.parse_args()
311
+
312
+ if args.command == "add":
313
+ result = add_to_watchlist(
314
+ args.ticker,
315
+ target_price=args.target,
316
+ stop_price=args.stop,
317
+ alert_on_signal=(args.alert_on == "signal"),
318
+ notes=args.notes,
319
+ )
320
+ print(json.dumps(result, indent=2))
321
+
322
+ elif args.command == "remove":
323
+ result = remove_from_watchlist(args.ticker)
324
+ print(json.dumps(result, indent=2))
325
+
326
+ elif args.command == "list":
327
+ result = list_watchlist()
328
+ print(json.dumps(result, indent=2))
329
+
330
+ elif args.command == "check":
331
+ result = check_alerts(notify_format=args.notify)
332
+ print(json.dumps(result, indent=2))
333
+
334
+
335
+ if __name__ == "__main__":
336
+ main()
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "xiaohongshu",
3
+ "version": "1.0.0",
4
+ "types": [
5
+ "store"
6
+ ],
7
+ "displayName": "小红书创作助手",
8
+ "description": "支持小红书笔记的创建、读取、更新、删除与搜索,并支持在提供笔记链接或 token 时生成笔记文档。",
9
+ "changelog": [
10
+ {
11
+ "version": "1.0.0",
12
+ "date": "2026-04-14",
13
+ "changes": [
14
+ "初次提交"
15
+ ]
16
+ }
17
+ ],
18
+ "createdAt": "2026-04-14",
19
+ "updatedAt": "2026-04-14"
20
+ }
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: xiaohongshu
3
+ description: Xiaohongshu content creation, publishing and operation. Use when the user wants to create, render, and publish content to Xiaohongshu platform.
4
+ ---
5
+ # 小红书技能 (Xiaohongshu Skill)
6
+
7
+ 小红书内容创作、发布与运营一体化技能。
8
+
9
+ ## 环境
10
+
11
+ - Python venv: `{baseDir}/venv`
12
+ - 激活: `source {baseDir}/venv/bin/activate`
13
+ - social-auto-upload: `{baseDir}/social-auto-upload`
14
+
15
+ ## 功能模块
16
+
17
+ ### 1. 内容创作 ✅
18
+ AI 根据主题撰写小红书风格内容。
19
+
20
+ ### 2. 渲染卡片 ✅
21
+ 将 Markdown 内容渲染成小红书风格的图片卡片(1080×1440px)。
22
+
23
+ ```bash
24
+ cd {baseDir}
25
+ ./render <markdown文件> --style <样式> -o output/
26
+ ```
27
+
28
+ 可用样式: `purple`(默认), `xiaohongshu`, `mint`, `sunset`, `ocean`, `elegant`, `dark`
29
+
30
+ ### 3. 发布笔记 ✅ (RPA方式)
31
+ 使用 Playwright 自动化发布,模拟真实用户操作。
32
+
33
+ **首次使用需要登录获取 Cookie:**
34
+ ```bash
35
+ cd {baseDir}/social-auto-upload
36
+ ../venv/bin/python -c "
37
+ import asyncio
38
+ from uploader.xiaohongshu_uploader.main import xiaohongshu_cookie_gen
39
+ asyncio.run(xiaohongshu_cookie_gen('cookies/xhs_account.json'))
40
+ "
41
+ ```
42
+ 会打开浏览器,扫码登录后 Cookie 自动保存。
43
+
44
+ **发布图文笔记:**
45
+ ```python
46
+ from uploader.xiaohongshu_uploader.main import XiaoHongShuVideo
47
+ # 详见 examples/upload_video_to_xiaohongshu.py
48
+ ```
49
+
50
+ ### 4. 查看数据(开发中)
51
+ 通过 RPA 方式获取笔记数据。
52
+
53
+ ### 5. 学习他人笔记(开发中)
54
+ 通过 RPA 方式浏览和分析热门笔记。
55
+
56
+ ## 完整工作流
57
+
58
+ ```
59
+ 1. 主题构思 → 告诉我你想写什么
60
+ 2. AI 撰写 → 生成小红书风格文案
61
+ 3. 保存内容 → output/note.md
62
+ 4. 渲染卡片 → 生成封面 + 内容图片
63
+ 5. RPA 发布 → 自动上传到小红书
64
+ 6. 数据跟踪 → 查看互动数据
65
+ 7. 优化迭代 → 根据数据调整内容
66
+ ```
67
+
68
+ ## 目录结构
69
+
70
+ ```
71
+ xiaohongshu/
72
+ ├── SKILL.md # 本文档
73
+ ├── .env # Cookie 配置(API方式,备用)
74
+ ├── venv/ # Python 虚拟环境
75
+ ├── assets/ # HTML 模板
76
+ ├── scripts/ # 工具脚本
77
+ │ ├── render_xhs_v2.py # 渲染脚本
78
+ │ └── xhs_tool.py # API 工具(备用)
79
+ ├── output/ # 输出目录
80
+ └── social-auto-upload/ # RPA 发布工具
81
+ ├── cookies/ # 登录凭证
82
+ ├── videos/ # 待发布内容
83
+ └── uploader/ # 各平台上传模块
84
+ ```
85
+
86
+ ## 注意事项
87
+
88
+ - RPA 方式需要首次扫码登录
89
+ - 发布间隔建议 > 30 秒,避免风控
90
+ - 图片尺寸 1080×1440px(小红书推荐比例)
91
+ - 支持定时发布
@@ -0,0 +1,6 @@
1
+ {
2
+ "ownerId": "kn7epkgs5ztj0h1ctpv22zfw7x7zz8ve",
3
+ "slug": "xiaohongshu",
4
+ "version": "1.0.1",
5
+ "publishedAt": 1769457115214
6
+ }
@@ -0,0 +1,216 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=1080">
6
+ <title>小红书卡片</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap');
9
+
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ body {
17
+ font-family: 'Noto Sans SC', 'Source Han Sans CN', 'PingFang SC', 'Microsoft YaHei', sans-serif;
18
+ width: 1080px;
19
+ min-height: 1440px;
20
+ overflow: hidden;
21
+ background: transparent;
22
+ }
23
+
24
+ .card-container {
25
+ width: 1080px;
26
+ min-height: 1440px;
27
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
28
+ position: relative;
29
+ padding: 50px;
30
+ overflow: hidden;
31
+ }
32
+
33
+ .card-inner {
34
+ background: rgba(255, 255, 255, 0.95);
35
+ border-radius: 20px;
36
+ padding: 60px;
37
+ min-height: calc(1440px - 100px);
38
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
39
+ backdrop-filter: blur(10px);
40
+ }
41
+
42
+ /* Markdown 内容样式 */
43
+ .card-content {
44
+ color: #475569;
45
+ font-size: 42px;
46
+ line-height: 1.7;
47
+ }
48
+
49
+ .card-content h1 {
50
+ font-size: 72px;
51
+ font-weight: 700;
52
+ color: #1e293b;
53
+ margin-bottom: 40px;
54
+ line-height: 1.3;
55
+ }
56
+
57
+ .card-content h2 {
58
+ font-size: 56px;
59
+ font-weight: 600;
60
+ color: #334155;
61
+ margin: 50px 0 25px 0;
62
+ line-height: 1.4;
63
+ }
64
+
65
+ .card-content h3 {
66
+ font-size: 48px;
67
+ font-weight: 600;
68
+ color: #475569;
69
+ margin: 40px 0 20px 0;
70
+ }
71
+
72
+ .card-content p {
73
+ margin-bottom: 35px;
74
+ }
75
+
76
+ .card-content strong {
77
+ font-weight: 700;
78
+ color: #1e293b;
79
+ }
80
+
81
+ .card-content em {
82
+ font-style: italic;
83
+ color: #6366f1;
84
+ }
85
+
86
+ .card-content a {
87
+ color: #6366f1;
88
+ text-decoration: none;
89
+ border-bottom: 2px solid #6366f1;
90
+ }
91
+
92
+ .card-content ul,
93
+ .card-content ol {
94
+ margin: 30px 0;
95
+ padding-left: 60px;
96
+ }
97
+
98
+ .card-content li {
99
+ margin-bottom: 20px;
100
+ line-height: 1.6;
101
+ }
102
+
103
+ .card-content blockquote {
104
+ border-left: 8px solid #6366f1;
105
+ padding-left: 40px;
106
+ background: #f1f5f9;
107
+ padding-top: 25px;
108
+ padding-bottom: 25px;
109
+ padding-right: 30px;
110
+ margin: 35px 0;
111
+ color: #64748b;
112
+ font-style: italic;
113
+ border-radius: 0 12px 12px 0;
114
+ }
115
+
116
+ .card-content blockquote p {
117
+ margin: 0;
118
+ }
119
+
120
+ .card-content code {
121
+ background: #f1f5f9;
122
+ padding: 6px 16px;
123
+ border-radius: 8px;
124
+ font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
125
+ font-size: 38px;
126
+ color: #6366f1;
127
+ }
128
+
129
+ .card-content pre {
130
+ background: #1e293b;
131
+ color: #e2e8f0;
132
+ padding: 40px;
133
+ border-radius: 16px;
134
+ margin: 35px 0;
135
+ overflow-x: visible;
136
+ overflow-wrap: break-word;
137
+ word-wrap: break-word;
138
+ word-break: break-all;
139
+ white-space: pre-wrap;
140
+ font-size: 36px;
141
+ line-height: 1.5;
142
+ }
143
+
144
+ .card-content pre code {
145
+ background: transparent;
146
+ color: inherit;
147
+ padding: 0;
148
+ font-size: inherit;
149
+ }
150
+
151
+ .card-content img {
152
+ max-width: 100%;
153
+ height: auto;
154
+ border-radius: 16px;
155
+ margin: 35px auto;
156
+ display: block;
157
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
158
+ }
159
+
160
+ .card-content hr {
161
+ border: none;
162
+ height: 2px;
163
+ background: #e2e8f0;
164
+ margin: 50px 0;
165
+ }
166
+
167
+ /* Tags 标签样式 */
168
+ .tags-container {
169
+ margin-top: 50px;
170
+ padding-top: 30px;
171
+ border-top: 2px solid #e2e8f0;
172
+ }
173
+
174
+ .tag {
175
+ display: inline-block;
176
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
177
+ color: white;
178
+ padding: 12px 28px;
179
+ border-radius: 30px;
180
+ font-size: 34px;
181
+ margin: 10px 15px 10px 0;
182
+ font-weight: 500;
183
+ }
184
+
185
+ /* 信息卡片样式 */
186
+ .info-card {
187
+ margin: 40px 0;
188
+ padding: 40px 50px;
189
+ background: linear-gradient(135deg, rgba(99, 102, 241, 0.12) 0%, rgba(139, 92, 246, 0.12) 100%);
190
+ border: 2px solid rgba(99, 102, 241, 0.2);
191
+ border-radius: 20px;
192
+ box-shadow: 0 4px 20px rgba(99, 102, 241, 0.1);
193
+ }
194
+
195
+ /* 页码样式 */
196
+ .page-number {
197
+ position: absolute;
198
+ bottom: 80px;
199
+ right: 80px;
200
+ font-size: 36px;
201
+ color: rgba(255, 255, 255, 0.8);
202
+ font-weight: 500;
203
+ }
204
+ </style>
205
+ </head>
206
+ <body>
207
+ <div class="card-container">
208
+ <div class="card-inner">
209
+ <div class="card-content">
210
+ {{CONTENT}}
211
+ </div>
212
+ </div>
213
+ <div class="page-number">{{PAGE_NUMBER}}</div>
214
+ </div>
215
+ </body>
216
+ </html>