thinking-phrases 1.0.1 → 2.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 (41) hide show
  1. package/README.md +230 -142
  2. package/configs/hn-top.config.json +60 -27
  3. package/launchd/rss-update.error.log +3 -27
  4. package/launchd/rss-update.log +308 -0
  5. package/launchd/task-health.json +54 -0
  6. package/out/dwyl-quotes.json +1621 -0
  7. package/out/javascript-tips.json +107 -0
  8. package/out/league-loading-screen-tips.json +107 -0
  9. package/out/ruby-tips.json +115 -0
  10. package/out/settings-linux.json +87 -0
  11. package/out/settings-mac.json +87 -0
  12. package/out/settings-windows.json +87 -0
  13. package/out/typescript-tips.json +131 -0
  14. package/out/vscode-tips.json +87 -0
  15. package/out/wow-loading-screen-tips.json +116 -0
  16. package/package.json +19 -12
  17. package/scripts/build.ts +3 -3
  18. package/scripts/debug-hn-hydration.ts +33 -0
  19. package/scripts/run-rss-update.zsh +25 -3
  20. package/scripts/show-thinking-phrases-health.ts +74 -0
  21. package/scripts/trigger-thinking-phrases-scheduler.zsh +50 -0
  22. package/src/core/config.ts +65 -3
  23. package/src/core/githubModels.ts +200 -112
  24. package/src/core/interactive.ts +49 -67
  25. package/src/core/phraseCache.ts +242 -0
  26. package/src/core/phraseFormats.ts +243 -0
  27. package/src/core/presets.ts +1 -1
  28. package/src/core/runner.ts +246 -113
  29. package/src/core/scheduler.ts +1 -1
  30. package/src/core/taskHealth.ts +213 -0
  31. package/src/core/types.ts +32 -8
  32. package/src/core/utils.ts +27 -2
  33. package/src/sources/customJson.ts +28 -18
  34. package/src/sources/earthquakes.ts +4 -4
  35. package/src/sources/githubActivity.ts +120 -48
  36. package/src/sources/hackerNews.ts +19 -7
  37. package/src/sources/rss.ts +25 -11
  38. package/src/sources/stocks.ts +31 -10
  39. package/src/sources/weatherAlerts.ts +173 -7
  40. package/tsconfig.json +1 -1
  41. package/scripts/update-rss-settings.ts +0 -7
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Centralized phrase format definitions.
3
+ *
4
+ * Every display-string pattern lives here so there's one place to
5
+ * tweak separators, ordering, or punctuation across all sources.
6
+ *
7
+ * Each formatter accepts an optional `template` string with %varName%
8
+ * placeholders. When provided, the template drives the output. When
9
+ * omitted, the hardcoded default format is used (backward-compatible).
10
+ */
11
+
12
+ /** Separator used between phrase segments (e.g. source, title, time). */
13
+ export const PHRASE_SEPARATOR = ' — ';
14
+
15
+ // ── Template Engine ─────────────────────────────────────────────────
16
+
17
+ /**
18
+ * Substitute %varName% placeholders with values from `vars`.
19
+ * - Missing / empty / whitespace-only vars are removed.
20
+ * - Dangling separators and wrapper chars like `()` `[]` are cleaned up.
21
+ */
22
+ export function applyFormatTemplate(
23
+ template: string,
24
+ vars: Record<string, string | undefined>,
25
+ ): string {
26
+ let result = template;
27
+
28
+ // Replace every %key% with its value, or empty string if missing
29
+ result = result.replace(/%([a-zA-Z_]\w*)%/gu, (_match, key: string) => {
30
+ const value = vars[key]?.trim();
31
+ return value || '';
32
+ });
33
+
34
+ // Remove empty wrapper pairs: (), [], {}
35
+ result = result.replace(/\(\s*\)/gu, '');
36
+ result = result.replace(/\[\s*\]/gu, '');
37
+ result = result.replace(/\{\s*\}/gu, '');
38
+
39
+ // Collapse repeated separators (handles — , | , - , etc.)
40
+ result = result.replace(/\s*(?:—|\||-|•|·)\s*(?:(?:—|\||-|•|·)\s*)*/gu, (match) => {
41
+ // Keep just the first separator character with its original spacing pattern
42
+ const sepMatch = match.match(/\s*(—|\||-|•|·)\s*/u);
43
+ return sepMatch ? ` ${sepMatch[1]} ` : ' ';
44
+ });
45
+
46
+ // Clean up multiple spaces
47
+ result = result.replace(/\s{2,}/gu, ' ');
48
+
49
+ // Remove leading/trailing separators
50
+ result = result.replace(/^\s*(?:—|\||-|•|·)\s*/u, '');
51
+ result = result.replace(/\s*(?:—|\||-|•|·)\s*$/u, '');
52
+
53
+ return result.trim();
54
+ }
55
+
56
+ // ── Article (RSS, Earthquakes, Weather alerts, Custom JSON) ─────────
57
+
58
+ export interface ArticlePhraseVars {
59
+ source?: string;
60
+ title: string;
61
+ time?: string;
62
+ }
63
+
64
+ export interface ArticlePhraseOpts {
65
+ includeSource?: boolean;
66
+ includeTime?: boolean;
67
+ template?: string;
68
+ }
69
+
70
+ /** Default: "Title — Source (3h ago)" */
71
+ export function formatArticlePhrase(
72
+ vars: ArticlePhraseVars,
73
+ opts?: ArticlePhraseOpts,
74
+ ): string {
75
+ if (opts?.template) {
76
+ return applyFormatTemplate(opts.template, { ...vars });
77
+ }
78
+
79
+ const parts: string[] = [vars.title.trim()];
80
+ const suffix = [
81
+ opts?.includeSource !== false && vars.source?.trim() ? vars.source.trim() : '',
82
+ opts?.includeTime !== false && vars.time?.trim() ? `(${vars.time.trim()})` : '',
83
+ ].filter(Boolean).join(' ');
84
+
85
+ if (suffix) {
86
+ parts.push(suffix);
87
+ }
88
+
89
+ return parts.join(PHRASE_SEPARATOR);
90
+ }
91
+
92
+ // ── Hacker News ─────────────────────────────────────────────────────
93
+
94
+ export interface HackerNewsPhraseVars {
95
+ title: string;
96
+ score?: string;
97
+ time?: string;
98
+ }
99
+
100
+ export interface HackerNewsPhraseOpts {
101
+ template?: string;
102
+ }
103
+
104
+ /** Default: "HN: Title — +342 — 3h ago" */
105
+ export function formatHackerNewsPhrase(vars: HackerNewsPhraseVars, opts?: HackerNewsPhraseOpts): string {
106
+ if (opts?.template) {
107
+ return applyFormatTemplate(opts.template, { ...vars });
108
+ }
109
+
110
+ return [`HN: ${vars.title}`, vars.score, vars.time]
111
+ .filter(Boolean)
112
+ .join(PHRASE_SEPARATOR);
113
+ }
114
+
115
+ // ── Stocks ──────────────────────────────────────────────────────────
116
+
117
+ export interface StockPhraseVars {
118
+ symbol: string;
119
+ price: string;
120
+ change?: string;
121
+ market?: string;
122
+ }
123
+
124
+ export interface StockPhraseOpts {
125
+ template?: string;
126
+ }
127
+
128
+ /** Default: "MSFT $425.30 ▲ 1.25% 🟢" */
129
+ export function formatStockPhrase(vars: StockPhraseVars, opts?: StockPhraseOpts): string {
130
+ if (opts?.template) {
131
+ return applyFormatTemplate(opts.template, { ...vars });
132
+ }
133
+
134
+ return [vars.symbol, vars.price, vars.change, vars.market]
135
+ .filter(Boolean)
136
+ .join(' ');
137
+ }
138
+
139
+ // ── GitHub Commits ──────────────────────────────────────────────────
140
+
141
+ export interface GitHubCommitPhraseVars {
142
+ headline?: string;
143
+ delta?: string;
144
+ repo: string;
145
+ sha?: string;
146
+ author?: string;
147
+ time?: string;
148
+ }
149
+
150
+ export interface GitHubCommitPhraseOpts {
151
+ template?: string;
152
+ }
153
+
154
+ /** Default: "Fix null check (+12 -3) vscode@a1b2c3d - @octocat 2h ago" */
155
+ export function formatGitHubCommitPhrase(vars: GitHubCommitPhraseVars, opts?: GitHubCommitPhraseOpts): string {
156
+ if (opts?.template) {
157
+ return applyFormatTemplate(opts.template, { ...vars });
158
+ }
159
+
160
+ const metadata = [
161
+ vars.delta ? `(${vars.delta})` : undefined,
162
+ `${vars.repo}${vars.sha ? `@${vars.sha}` : ''}`,
163
+ vars.author ? `- @${vars.author}` : undefined,
164
+ vars.time,
165
+ ].filter(Boolean);
166
+
167
+ return [vars.headline, ...metadata].filter(Boolean).join(' ');
168
+ }
169
+
170
+ // ── GitHub Feed / Org Events ────────────────────────────────────────
171
+
172
+ export interface GitHubFeedPhraseVars {
173
+ handle?: string;
174
+ action: string;
175
+ time?: string;
176
+ }
177
+
178
+ export interface GitHubFeedPhraseOpts {
179
+ template?: string;
180
+ }
181
+
182
+ /** Default: "@octocat pushed to main — 1h ago" */
183
+ export function formatGitHubFeedPhrase(vars: GitHubFeedPhraseVars, opts?: GitHubFeedPhraseOpts): string {
184
+ if (opts?.template) {
185
+ return applyFormatTemplate(opts.template, { ...vars });
186
+ }
187
+
188
+ const titlePart = vars.handle
189
+ ? `@${vars.handle} ${vars.action}`
190
+ : vars.action;
191
+
192
+ return vars.time
193
+ ? `${titlePart}${PHRASE_SEPARATOR}${vars.time}`
194
+ : titlePart;
195
+ }
196
+
197
+ // ── Weather No-Alerts ───────────────────────────────────────────────
198
+
199
+ export interface WeatherNoAlertsPhraseVars {
200
+ location: string;
201
+ }
202
+
203
+ /** "No active alerts — Fort Lauderdale, FL — Weather.gov" */
204
+ export function formatWeatherNoAlertsPhrase(vars: WeatherNoAlertsPhraseVars): string {
205
+ return `No active alerts${PHRASE_SEPARATOR}${vars.location}${PHRASE_SEPARATOR}Weather.gov`;
206
+ }
207
+
208
+ // ── Source Suffix ───────────────────────────────────────────────────
209
+
210
+ /**
211
+ * Build a source suffix from article metadata.
212
+ * Format varies by source type:
213
+ * HN: "— HN @author 123 pts (2h ago)"
214
+ * GitHub: "— repo +12/-3 @user (5m ago)"
215
+ * RSS: "— GitHub Blog (3h ago)"
216
+ */
217
+ export function appendSourceSuffix(
218
+ phrase: string,
219
+ source?: string,
220
+ time?: string,
221
+ metadata?: Record<string, string | undefined>,
222
+ ): string {
223
+ const parts: string[] = [];
224
+
225
+ if (metadata?.repo && metadata?.delta) {
226
+ // GitHub commit: "repo +12/-3 @user"
227
+ parts.push(metadata.repo);
228
+ parts.push(metadata.delta);
229
+ if (metadata.author) parts.push(metadata.author);
230
+ } else if (metadata?.score) {
231
+ // Hacker News: "HN @author 123 pts"
232
+ if (source) parts.push(source);
233
+ if (metadata.author) parts.push(`@${metadata.author}`);
234
+ parts.push(metadata.score);
235
+ } else if (source) {
236
+ parts.push(source);
237
+ }
238
+
239
+ if (time) parts.push(`(${time})`);
240
+
241
+ const suffix = parts.join(' ');
242
+ return suffix ? `${phrase}${PHRASE_SEPARATOR}${suffix}` : phrase;
243
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Config } from './types.js';
2
2
 
3
3
  type ConfigPresetConfig = {
4
- [K in keyof Config]?: Config[K] extends Array<infer T>
4
+ [K in keyof Config]?: Config[K] extends (infer T)[]
5
5
  ? T[]
6
6
  : Config[K] extends object
7
7
  ? Partial<Config[K]>