@lobu/cli 6.0.1 → 7.0.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 (265) hide show
  1. package/README.md +20 -27
  2. package/dist/bundled-skills/lobu/SKILL.md +11 -11
  3. package/dist/commands/_lib/apply/apply-cmd.d.ts +38 -0
  4. package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
  5. package/dist/commands/_lib/apply/apply-cmd.js +574 -40
  6. package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
  7. package/dist/commands/_lib/apply/client.d.ts +180 -1
  8. package/dist/commands/_lib/apply/client.d.ts.map +1 -1
  9. package/dist/commands/_lib/apply/client.js +308 -28
  10. package/dist/commands/_lib/apply/client.js.map +1 -1
  11. package/dist/commands/_lib/apply/desired-state.d.ts +134 -3
  12. package/dist/commands/_lib/apply/desired-state.d.ts.map +1 -1
  13. package/dist/commands/_lib/apply/desired-state.js +703 -89
  14. package/dist/commands/_lib/apply/desired-state.js.map +1 -1
  15. package/dist/commands/_lib/apply/diff.d.ts +61 -3
  16. package/dist/commands/_lib/apply/diff.d.ts.map +1 -1
  17. package/dist/commands/_lib/apply/diff.js +382 -92
  18. package/dist/commands/_lib/apply/diff.js.map +1 -1
  19. package/dist/commands/_lib/apply/prompt.d.ts +6 -0
  20. package/dist/commands/_lib/apply/prompt.d.ts.map +1 -1
  21. package/dist/commands/_lib/apply/prompt.js +16 -0
  22. package/dist/commands/_lib/apply/prompt.js.map +1 -1
  23. package/dist/commands/_lib/apply/render.d.ts +9 -0
  24. package/dist/commands/_lib/apply/render.d.ts.map +1 -1
  25. package/dist/commands/_lib/apply/render.js +80 -3
  26. package/dist/commands/_lib/apply/render.js.map +1 -1
  27. package/dist/commands/agent.d.ts +7 -0
  28. package/dist/commands/agent.d.ts.map +1 -1
  29. package/dist/commands/agent.js +65 -1
  30. package/dist/commands/agent.js.map +1 -1
  31. package/dist/commands/chat.d.ts +12 -9
  32. package/dist/commands/chat.d.ts.map +1 -1
  33. package/dist/commands/chat.js +125 -57
  34. package/dist/commands/chat.js.map +1 -1
  35. package/dist/commands/dev.d.ts +23 -7
  36. package/dist/commands/dev.d.ts.map +1 -1
  37. package/dist/commands/dev.js +197 -49
  38. package/dist/commands/dev.js.map +1 -1
  39. package/dist/commands/doctor.d.ts +1 -0
  40. package/dist/commands/doctor.d.ts.map +1 -1
  41. package/dist/commands/doctor.js +136 -0
  42. package/dist/commands/doctor.js.map +1 -1
  43. package/dist/commands/eval.d.ts +8 -0
  44. package/dist/commands/eval.d.ts.map +1 -1
  45. package/dist/commands/eval.js +72 -6
  46. package/dist/commands/eval.js.map +1 -1
  47. package/dist/commands/init.d.ts +22 -5
  48. package/dist/commands/init.d.ts.map +1 -1
  49. package/dist/commands/init.js +355 -182
  50. package/dist/commands/init.js.map +1 -1
  51. package/dist/commands/link.d.ts +11 -0
  52. package/dist/commands/link.d.ts.map +1 -0
  53. package/dist/commands/link.js +28 -0
  54. package/dist/commands/link.js.map +1 -0
  55. package/dist/commands/login.d.ts.map +1 -1
  56. package/dist/commands/login.js +14 -2
  57. package/dist/commands/login.js.map +1 -1
  58. package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
  59. package/dist/commands/memory/_lib/browser-auth-cmd.js +3 -3
  60. package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
  61. package/dist/commands/memory/_lib/mcp.d.ts +2 -2
  62. package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
  63. package/dist/commands/memory/_lib/mcp.js +24 -12
  64. package/dist/commands/memory/_lib/mcp.js.map +1 -1
  65. package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
  66. package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
  67. package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
  68. package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
  69. package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
  70. package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
  71. package/dist/commands/memory/_lib/schema.d.ts +29 -2
  72. package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
  73. package/dist/commands/memory/_lib/schema.js +121 -5
  74. package/dist/commands/memory/_lib/schema.js.map +1 -1
  75. package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
  76. package/dist/commands/memory/_lib/seed-cmd.js +46 -24
  77. package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
  78. package/dist/commands/memory/run.d.ts.map +1 -1
  79. package/dist/commands/memory/run.js +2 -2
  80. package/dist/commands/memory/run.js.map +1 -1
  81. package/dist/commands/org.d.ts +4 -0
  82. package/dist/commands/org.d.ts.map +1 -1
  83. package/dist/commands/org.js +10 -0
  84. package/dist/commands/org.js.map +1 -1
  85. package/dist/commands/platforms/platform-prompts.d.ts +0 -1
  86. package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
  87. package/dist/commands/platforms/platform-prompts.js +54 -8
  88. package/dist/commands/platforms/platform-prompts.js.map +1 -1
  89. package/dist/commands/telemetry.d.ts +10 -0
  90. package/dist/commands/telemetry.d.ts.map +1 -0
  91. package/dist/commands/telemetry.js +68 -0
  92. package/dist/commands/telemetry.js.map +1 -0
  93. package/dist/commands/token.d.ts +9 -0
  94. package/dist/commands/token.d.ts.map +1 -1
  95. package/dist/commands/token.js +54 -0
  96. package/dist/commands/token.js.map +1 -1
  97. package/dist/commands/whoami.d.ts.map +1 -1
  98. package/dist/commands/whoami.js +1 -1
  99. package/dist/commands/whoami.js.map +1 -1
  100. package/dist/connectors/README.md +534 -0
  101. package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
  102. package/dist/connectors/apple_health.ts +138 -0
  103. package/dist/connectors/apple_screen_time.ts +82 -0
  104. package/dist/connectors/browser-scraper-utils.ts +246 -0
  105. package/dist/connectors/capterra.ts +277 -0
  106. package/dist/connectors/g2.ts +290 -0
  107. package/dist/connectors/github.ts +1530 -0
  108. package/dist/connectors/glassdoor.ts +295 -0
  109. package/dist/connectors/gmaps.ts +197 -0
  110. package/dist/connectors/google_calendar.ts +641 -0
  111. package/dist/connectors/google_gmail.ts +754 -0
  112. package/dist/connectors/google_photos.ts +776 -0
  113. package/dist/connectors/google_play.ts +349 -0
  114. package/dist/connectors/hackernews.ts +471 -0
  115. package/dist/connectors/index.ts +28 -0
  116. package/dist/connectors/ios_appstore.ts +226 -0
  117. package/dist/connectors/linkedin.ts +494 -0
  118. package/dist/connectors/local_directory.ts +91 -0
  119. package/dist/connectors/microsoft_outlook.ts +410 -0
  120. package/dist/connectors/producthunt.ts +471 -0
  121. package/dist/connectors/reddit.ts +600 -0
  122. package/dist/connectors/revolut.ts +572 -0
  123. package/dist/connectors/rss.ts +448 -0
  124. package/dist/connectors/spotify.ts +590 -0
  125. package/dist/connectors/trustpilot.ts +203 -0
  126. package/dist/connectors/website.ts +629 -0
  127. package/dist/connectors/whatsapp.ts +1081 -0
  128. package/dist/connectors/whatsapp_local.ts +125 -0
  129. package/dist/connectors/x.ts +536 -0
  130. package/dist/connectors/youtube.ts +666 -0
  131. package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
  132. package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
  133. package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
  134. package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
  135. package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
  136. package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
  137. package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
  138. package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
  139. package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
  140. package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
  141. package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
  142. package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
  143. package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
  144. package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
  145. package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
  146. package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
  147. package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
  148. package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
  149. package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
  150. package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
  151. package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
  152. package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
  153. package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
  154. package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
  155. package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
  156. package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
  157. package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
  158. package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
  159. package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
  160. package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
  161. package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
  162. package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
  163. package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
  164. package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
  165. package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
  166. package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
  167. package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
  168. package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
  169. package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
  170. package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
  171. package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
  172. package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
  173. package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
  174. package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
  175. package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
  176. package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
  177. package/dist/db/migrations/20260510220000_connector_required_capability.sql +47 -0
  178. package/dist/db/migrations/20260512000000_device_worker_connection_binding.sql +113 -0
  179. package/dist/db/migrations/20260512131703_connections_slug.sql +131 -0
  180. package/dist/db/migrations/20260513000000_chat_user_identities.sql +24 -0
  181. package/dist/db/migrations/20260513120000_auth_profiles_device_binding.sql +50 -0
  182. package/dist/db/migrations/20260513150000_auth_profiles_cdp_url.sql +43 -0
  183. package/dist/db/migrations/20260513200000_notifications_as_events.sql +86 -0
  184. package/dist/db/migrations/20260514000000_scheduled_jobs.sql +97 -0
  185. package/dist/db/migrations/20260514120000_auth_profiles_connector_key_nullable.sql +42 -0
  186. package/dist/eval/types.d.ts +2 -0
  187. package/dist/eval/types.d.ts.map +1 -1
  188. package/dist/index.d.ts +11 -0
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +210 -132
  191. package/dist/index.js.map +1 -1
  192. package/dist/internal/api-client.d.ts +4 -8
  193. package/dist/internal/api-client.d.ts.map +1 -1
  194. package/dist/internal/api-client.js +1 -1
  195. package/dist/internal/api-client.js.map +1 -1
  196. package/dist/internal/context.js +2 -2
  197. package/dist/internal/context.js.map +1 -1
  198. package/dist/internal/credentials.d.ts.map +1 -1
  199. package/dist/internal/credentials.js +6 -1
  200. package/dist/internal/credentials.js.map +1 -1
  201. package/dist/internal/gateway-url.d.ts +14 -0
  202. package/dist/internal/gateway-url.d.ts.map +1 -1
  203. package/dist/internal/gateway-url.js +19 -0
  204. package/dist/internal/gateway-url.js.map +1 -1
  205. package/dist/internal/index.d.ts +3 -4
  206. package/dist/internal/index.d.ts.map +1 -1
  207. package/dist/internal/index.js +3 -3
  208. package/dist/internal/index.js.map +1 -1
  209. package/dist/internal/oauth.d.ts +6 -5
  210. package/dist/internal/oauth.d.ts.map +1 -1
  211. package/dist/internal/oauth.js +2 -2
  212. package/dist/internal/project-link.d.ts +10 -0
  213. package/dist/internal/project-link.d.ts.map +1 -0
  214. package/dist/internal/project-link.js +48 -0
  215. package/dist/internal/project-link.js.map +1 -0
  216. package/dist/providers.json +2 -2
  217. package/dist/server.bundle.mjs +31654 -30866
  218. package/dist/start-local.bundle.mjs +74409 -0
  219. package/dist/templates/README.md.tmpl +10 -11
  220. package/dist/templates/TESTING.md.tmpl +9 -9
  221. package/package.json +15 -13
  222. package/dist/__tests__/chat.integration.test.d.ts +0 -2
  223. package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
  224. package/dist/__tests__/chat.integration.test.js +0 -337
  225. package/dist/__tests__/chat.integration.test.js.map +0 -1
  226. package/dist/__tests__/dev.test.d.ts +0 -2
  227. package/dist/__tests__/dev.test.d.ts.map +0 -1
  228. package/dist/__tests__/dev.test.js +0 -25
  229. package/dist/__tests__/dev.test.js.map +0 -1
  230. package/dist/__tests__/init-memory.test.d.ts +0 -2
  231. package/dist/__tests__/init-memory.test.d.ts.map +0 -1
  232. package/dist/__tests__/init-memory.test.js +0 -45
  233. package/dist/__tests__/init-memory.test.js.map +0 -1
  234. package/dist/__tests__/token.test.d.ts +0 -2
  235. package/dist/__tests__/token.test.d.ts.map +0 -1
  236. package/dist/__tests__/token.test.js +0 -52
  237. package/dist/__tests__/token.test.js.map +0 -1
  238. package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
  239. package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
  240. package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
  241. package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
  242. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
  243. package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
  244. package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
  245. package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
  246. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
  247. package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
  248. package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
  249. package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
  250. package/dist/commands/apply.d.ts +0 -3
  251. package/dist/commands/apply.d.ts.map +0 -1
  252. package/dist/commands/apply.js +0 -5
  253. package/dist/commands/apply.js.map +0 -1
  254. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
  255. package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
  256. package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
  257. package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
  258. package/dist/internal/__tests__/api-client.test.d.ts +0 -2
  259. package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
  260. package/dist/internal/__tests__/api-client.test.js +0 -95
  261. package/dist/internal/__tests__/api-client.test.js.map +0 -1
  262. package/dist/internal/__tests__/context.test.d.ts +0 -2
  263. package/dist/internal/__tests__/context.test.d.ts.map +0 -1
  264. package/dist/internal/__tests__/context.test.js +0 -77
  265. package/dist/internal/__tests__/context.test.js.map +0 -1
@@ -0,0 +1,471 @@
1
+ /**
2
+ * HackerNews Connector (V1 runtime)
3
+ *
4
+ * Searches Hacker News stories and comments via the Algolia HN Search API.
5
+ * No authentication required.
6
+ */
7
+
8
+ import TurndownService from 'turndown';
9
+ import {
10
+ type ActionContext,
11
+ type ActionResult,
12
+ type ConnectorDefinition,
13
+ ConnectorRuntime,
14
+ calculateEngagementScore,
15
+ type EventEnvelope,
16
+ type SyncContext,
17
+ type SyncResult,
18
+ } from '@lobu/connector-sdk';
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Algolia HN API types
22
+ // ---------------------------------------------------------------------------
23
+
24
+ interface AlgoliaHit {
25
+ objectID: string;
26
+ created_at: string;
27
+ created_at_i: number;
28
+ author: string;
29
+ title?: string;
30
+ story_text?: string;
31
+ comment_text?: string;
32
+ url?: string;
33
+ points?: number;
34
+ num_comments?: number;
35
+ story_id?: number;
36
+ parent_id?: number;
37
+ _tags: string[];
38
+ }
39
+
40
+ interface AlgoliaResponse {
41
+ hits: AlgoliaHit[];
42
+ nbHits: number;
43
+ page: number;
44
+ nbPages: number;
45
+ hitsPerPage: number;
46
+ }
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Content-type tag mapping
50
+ // ---------------------------------------------------------------------------
51
+
52
+ const CONTENT_TYPE_TAG: Record<string, string> = {
53
+ story: 'story',
54
+ comment: 'comment',
55
+ ask_hn: 'ask_hn',
56
+ show_hn: 'show_hn',
57
+ };
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Connector
61
+ // ---------------------------------------------------------------------------
62
+
63
+ export default class HackerNewsConnector extends ConnectorRuntime {
64
+ readonly definition: ConnectorDefinition = {
65
+ key: 'hackernews',
66
+ name: 'Hacker News',
67
+ description: 'Searches Hacker News stories and comments via Algolia API.',
68
+ version: '1.0.0',
69
+ faviconDomain: 'news.ycombinator.com',
70
+ authSchema: {
71
+ methods: [{ type: 'none' }],
72
+ },
73
+ feeds: {
74
+ stories: {
75
+ key: 'stories',
76
+ name: 'Stories',
77
+ description: 'Search HN for stories, Ask HN, and Show HN posts.',
78
+ configSchema: {
79
+ type: 'object',
80
+ required: ['search_query'],
81
+ properties: {
82
+ search_query: {
83
+ type: 'string',
84
+ minLength: 1,
85
+ description: 'Search term',
86
+ },
87
+ story_type: {
88
+ type: 'string',
89
+ enum: ['story', 'ask_hn', 'show_hn'],
90
+ default: 'story',
91
+ description: 'Story type filter',
92
+ },
93
+ lookback_days: {
94
+ type: 'integer',
95
+ minimum: 1,
96
+ maximum: 730,
97
+ default: 365,
98
+ description: 'Lookback window in days',
99
+ },
100
+ search_fields: {
101
+ type: 'array',
102
+ items: {
103
+ type: 'string',
104
+ enum: ['title', 'url', 'story_text'],
105
+ },
106
+ default: ['title'],
107
+ description:
108
+ 'Algolia fields to search in. Defaults to title only. Add url and/or story_text for broader matching (may increase noise for common words like "notion" or "linear").',
109
+ },
110
+ },
111
+ },
112
+ eventKinds: {
113
+ story: {
114
+ description: 'A Hacker News story',
115
+ metadataSchema: {
116
+ type: 'object',
117
+ properties: {
118
+ story_type: { type: 'string', description: 'story, ask_hn, or show_hn' },
119
+ tags: { type: 'array', items: { type: 'string' } },
120
+ external_url: { type: 'string', format: 'uri' },
121
+ score: { type: 'number', description: 'HN points' },
122
+ reply_count: { type: 'number' },
123
+ },
124
+ },
125
+ },
126
+ ask_hn: {
127
+ description: 'An Ask HN post',
128
+ metadataSchema: {
129
+ type: 'object',
130
+ properties: {
131
+ story_type: { type: 'string' },
132
+ tags: { type: 'array', items: { type: 'string' } },
133
+ score: { type: 'number' },
134
+ reply_count: { type: 'number' },
135
+ },
136
+ },
137
+ },
138
+ show_hn: {
139
+ description: 'A Show HN post',
140
+ metadataSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ story_type: { type: 'string' },
144
+ tags: { type: 'array', items: { type: 'string' } },
145
+ external_url: { type: 'string', format: 'uri' },
146
+ score: { type: 'number' },
147
+ reply_count: { type: 'number' },
148
+ },
149
+ },
150
+ },
151
+ },
152
+ },
153
+ comments: {
154
+ key: 'comments',
155
+ name: 'Comments',
156
+ description: 'Search HN for comments.',
157
+ configSchema: {
158
+ type: 'object',
159
+ required: ['search_query'],
160
+ properties: {
161
+ search_query: {
162
+ type: 'string',
163
+ minLength: 1,
164
+ description: 'Search term',
165
+ },
166
+ lookback_days: {
167
+ type: 'integer',
168
+ minimum: 1,
169
+ maximum: 730,
170
+ default: 365,
171
+ description: 'Lookback window in days',
172
+ },
173
+ search_fields: {
174
+ type: 'array',
175
+ items: {
176
+ type: 'string',
177
+ enum: ['comment_text', 'author'],
178
+ },
179
+ default: ['comment_text'],
180
+ description: 'Algolia fields to search in. Defaults to comment_text.',
181
+ },
182
+ },
183
+ },
184
+ eventKinds: {
185
+ comment: {
186
+ description: 'A Hacker News comment',
187
+ metadataSchema: {
188
+ type: 'object',
189
+ properties: {
190
+ story_id: { type: 'number' },
191
+ parent_id: { type: 'number' },
192
+ tags: { type: 'array', items: { type: 'string' } },
193
+ },
194
+ },
195
+ },
196
+ },
197
+ },
198
+ },
199
+ optionsSchema: {
200
+ type: 'object',
201
+ required: ['search_query'],
202
+ properties: {
203
+ search_query: {
204
+ type: 'string',
205
+ minLength: 1,
206
+ description: 'Search term',
207
+ },
208
+ lookback_days: {
209
+ type: 'integer',
210
+ minimum: 1,
211
+ maximum: 730,
212
+ default: 365,
213
+ description: 'Lookback window in days',
214
+ },
215
+ },
216
+ },
217
+ };
218
+
219
+ private readonly BASE_URL = 'https://hn.algolia.com/api/v1';
220
+ private readonly ENGAGEMENT_THRESHOLD = 50;
221
+ private readonly CONTENT_FETCH_TIMEOUT = 5000;
222
+ private readonly MAX_PAGES = 50;
223
+ private readonly PAGE_DELAY_MS = 1000;
224
+ private readonly FETCH_DELAY_MS = 2000;
225
+ private turndownService: TurndownService;
226
+
227
+ constructor() {
228
+ super();
229
+ this.turndownService = new TurndownService({
230
+ headingStyle: 'atx',
231
+ codeBlockStyle: 'fenced',
232
+ });
233
+ }
234
+
235
+ // -------------------------------------------------------------------------
236
+ // sync
237
+ // -------------------------------------------------------------------------
238
+
239
+ async sync(ctx: SyncContext): Promise<SyncResult> {
240
+ const searchQuery = ctx.config.search_query as string;
241
+ const contentType =
242
+ ctx.feedKey === 'comments' ? 'comment' : ((ctx.config.story_type as string) ?? 'story');
243
+ const lookbackDays = (ctx.config.lookback_days as number) ?? 365;
244
+ const searchFields =
245
+ (ctx.config.search_fields as string[] | undefined) ??
246
+ (contentType === 'comment' ? ['comment_text'] : ['title']);
247
+
248
+ const lookbackTimestamp = Math.floor((Date.now() - lookbackDays * 86400000) / 1000);
249
+ const tag = CONTENT_TYPE_TAG[contentType] ?? 'story';
250
+
251
+ const events: EventEnvelope[] = [];
252
+ let page = 0;
253
+ let hasMore = true;
254
+
255
+ while (hasMore && page < this.MAX_PAGES) {
256
+ const url =
257
+ `${this.BASE_URL}/search?query=${encodeURIComponent(searchQuery)}` +
258
+ `&tags=${tag}&hitsPerPage=100&page=${page}` +
259
+ '&typoTolerance=false' +
260
+ `&restrictSearchableAttributes=${encodeURIComponent(searchFields.join(','))}` +
261
+ `&numericFilters=${encodeURIComponent(`created_at_i>${lookbackTimestamp}`)}`;
262
+
263
+ const response = await fetch(url);
264
+ if (!response.ok) {
265
+ throw new Error(`Algolia API error (${response.status}): ${await response.text()}`);
266
+ }
267
+
268
+ const data = (await response.json()) as AlgoliaResponse;
269
+
270
+ for (const hit of data.hits) {
271
+ if (contentType === 'comment') {
272
+ const event = this.transformComment(hit);
273
+ if (event) events.push(event);
274
+ } else {
275
+ events.push(this.transformStory(hit));
276
+ }
277
+ }
278
+
279
+ hasMore = data.page < data.nbPages - 1 && data.hits.length > 0;
280
+ page++;
281
+
282
+ if (hasMore) {
283
+ await this.sleep(this.PAGE_DELAY_MS);
284
+ }
285
+ }
286
+
287
+ // Enrich high-engagement stories with external content
288
+ if (contentType !== 'comment') {
289
+ await this.enrichStoriesWithExternalContent(events);
290
+ }
291
+
292
+ return {
293
+ events,
294
+ checkpoint: { last_sync_at: new Date().toISOString() },
295
+ metadata: { items_found: events.length },
296
+ };
297
+ }
298
+
299
+ // -------------------------------------------------------------------------
300
+ // execute
301
+ // -------------------------------------------------------------------------
302
+
303
+ async execute(_ctx: ActionContext): Promise<ActionResult> {
304
+ return { success: false, error: 'Actions not supported' };
305
+ }
306
+
307
+ // -------------------------------------------------------------------------
308
+ // Transform helpers
309
+ // -------------------------------------------------------------------------
310
+
311
+ private transformStory(hit: AlgoliaHit): EventEnvelope {
312
+ const isAskHN = hit._tags.includes('ask_hn');
313
+ const isShowHN = hit._tags.includes('show_hn');
314
+
315
+ let storyType = 'story';
316
+ let originType = 'story';
317
+ if (isAskHN) {
318
+ storyType = 'ask_hn';
319
+ originType = 'ask_hn';
320
+ } else if (isShowHN) {
321
+ storyType = 'show_hn';
322
+ originType = 'show_hn';
323
+ }
324
+
325
+ const engagementData = {
326
+ score: hit.points ?? 0,
327
+ reply_count: hit.num_comments ?? 0,
328
+ };
329
+
330
+ return {
331
+ origin_id: `hn_story_${hit.objectID}`,
332
+ title: hit.title ?? '',
333
+ payload_text: (hit.story_text ?? '').trim(),
334
+ author_name: hit.author,
335
+ source_url: `https://news.ycombinator.com/item?id=${hit.objectID}`,
336
+ occurred_at: new Date(hit.created_at_i * 1000),
337
+ origin_type: originType,
338
+ score: calculateEngagementScore('hackernews', engagementData),
339
+ metadata: {
340
+ type: 'story',
341
+ story_type: storyType,
342
+ tags: hit._tags,
343
+ external_url: hit.url,
344
+ created_at_i: hit.created_at_i,
345
+ score: hit.points ?? 0,
346
+ reply_count: hit.num_comments ?? 0,
347
+ },
348
+ };
349
+ }
350
+
351
+ private transformComment(hit: AlgoliaHit): EventEnvelope | null {
352
+ let parentExternalId: string | undefined;
353
+ if (hit.parent_id != null && hit.story_id != null && hit.parent_id !== hit.story_id) {
354
+ parentExternalId = `hn_comment_${hit.parent_id}`;
355
+ } else if (hit.story_id != null) {
356
+ parentExternalId = `hn_story_${hit.story_id}`;
357
+ }
358
+
359
+ if (!hit.comment_text) return null;
360
+
361
+ return {
362
+ origin_id: `hn_comment_${hit.objectID}`,
363
+ payload_text: hit.comment_text,
364
+ author_name: hit.author,
365
+ source_url: `https://news.ycombinator.com/item?id=${hit.objectID}`,
366
+ occurred_at: new Date(hit.created_at_i * 1000),
367
+ origin_type: 'comment',
368
+ score: calculateEngagementScore('hackernews', { score: 0 }),
369
+ origin_parent_id: parentExternalId,
370
+ metadata: {
371
+ type: 'comment',
372
+ story_id: hit.story_id,
373
+ parent_id: hit.parent_id,
374
+ created_at_i: hit.created_at_i,
375
+ tags: hit._tags,
376
+ },
377
+ };
378
+ }
379
+
380
+ // -------------------------------------------------------------------------
381
+ // External content enrichment
382
+ // -------------------------------------------------------------------------
383
+
384
+ private async enrichStoriesWithExternalContent(events: EventEnvelope[]): Promise<void> {
385
+ for (const event of events) {
386
+ const externalUrl = event.metadata?.external_url as string | undefined;
387
+ const points = event.metadata?.score as number | undefined;
388
+
389
+ if (!event.content && externalUrl && points != null && points >= this.ENGAGEMENT_THRESHOLD) {
390
+ const fetched = await this.fetchExternalContent(externalUrl);
391
+ if (fetched) {
392
+ event.content = fetched;
393
+ event.metadata = {
394
+ ...event.metadata,
395
+ fetched_content: true,
396
+ original_url: externalUrl,
397
+ };
398
+ }
399
+
400
+ await this.sleep(this.FETCH_DELAY_MS);
401
+ }
402
+ }
403
+ }
404
+
405
+ private async fetchExternalContent(url: string): Promise<string | null> {
406
+ try {
407
+ const controller = new AbortController();
408
+ const timeoutId = setTimeout(() => controller.abort(), this.CONTENT_FETCH_TIMEOUT);
409
+
410
+ const response = await fetch(url, {
411
+ signal: controller.signal,
412
+ headers: {
413
+ 'User-Agent': 'Mozilla/5.0 (compatible; HNBot/1.0)',
414
+ Accept: 'text/html,application/xhtml+xml',
415
+ },
416
+ });
417
+
418
+ clearTimeout(timeoutId);
419
+
420
+ if (!response.ok) return null;
421
+
422
+ const contentType = response.headers.get('content-type') ?? '';
423
+ if (!contentType.includes('text/html')) return null;
424
+
425
+ const html = await response.text();
426
+
427
+ // Strip non-article elements
428
+ const stripTags = [
429
+ 'script',
430
+ 'style',
431
+ 'noscript',
432
+ 'nav',
433
+ 'header',
434
+ 'footer',
435
+ 'aside',
436
+ 'iframe',
437
+ 'svg',
438
+ 'canvas',
439
+ 'video',
440
+ 'audio',
441
+ 'menu',
442
+ 'dialog',
443
+ 'embed',
444
+ 'object',
445
+ ];
446
+ let cleanHtml = html;
447
+ for (const tag of stripTags) {
448
+ cleanHtml = cleanHtml.replace(
449
+ new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}>`, 'gi'),
450
+ ''
451
+ );
452
+ }
453
+ cleanHtml = cleanHtml.replace(/<(link|meta|input)\b[^>]*\/?>/gi, '');
454
+
455
+ const markdown = this.turndownService.turndown(cleanHtml);
456
+ const trimmed = markdown.trim().substring(0, 2000);
457
+
458
+ return trimmed.length >= 100 ? trimmed : null;
459
+ } catch {
460
+ return null;
461
+ }
462
+ }
463
+
464
+ // -------------------------------------------------------------------------
465
+ // Utilities
466
+ // -------------------------------------------------------------------------
467
+
468
+ private sleep(ms: number): Promise<void> {
469
+ return new Promise((resolve) => setTimeout(resolve, ms));
470
+ }
471
+ }
@@ -0,0 +1,28 @@
1
+ export * from './apple_health.ts';
2
+ export * from './apple_screen_time.ts';
3
+ export * from './local_directory.ts';
4
+ export * from './browser-scraper-utils.ts';
5
+ export * from './capterra.ts';
6
+ export * from './g2.ts';
7
+ export * from './github.ts';
8
+ export * from './glassdoor.ts';
9
+ export * from './gmaps.ts';
10
+ export * from './google_calendar.ts';
11
+ export * from './google_gmail.ts';
12
+ export * from './google_photos.ts';
13
+ export * from './google_play.ts';
14
+ export * from './hackernews.ts';
15
+ export * from './ios_appstore.ts';
16
+ export * from './linkedin.ts';
17
+ export * from './microsoft_outlook.ts';
18
+ export * from './producthunt.ts';
19
+ export * from './reddit.ts';
20
+ export * from './revolut.ts';
21
+ export * from './rss.ts';
22
+ export * from './spotify.ts';
23
+ export * from './trustpilot.ts';
24
+ export * from './website.ts';
25
+ export * from './whatsapp.ts';
26
+ export * from './whatsapp_local.ts';
27
+ export * from './x.ts';
28
+ export * from './youtube.ts';