@telepat/snoopy 0.1.4

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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +255 -0
  3. package/dist/src/cli/commands/analytics.d.ts +3 -0
  4. package/dist/src/cli/commands/analytics.js +147 -0
  5. package/dist/src/cli/commands/analytics.js.map +1 -0
  6. package/dist/src/cli/commands/daemon.d.ts +5 -0
  7. package/dist/src/cli/commands/daemon.js +85 -0
  8. package/dist/src/cli/commands/daemon.js.map +1 -0
  9. package/dist/src/cli/commands/doctor.d.ts +1 -0
  10. package/dist/src/cli/commands/doctor.js +106 -0
  11. package/dist/src/cli/commands/doctor.js.map +1 -0
  12. package/dist/src/cli/commands/errors.d.ts +3 -0
  13. package/dist/src/cli/commands/errors.js +51 -0
  14. package/dist/src/cli/commands/errors.js.map +1 -0
  15. package/dist/src/cli/commands/export.d.ts +1 -0
  16. package/dist/src/cli/commands/export.js +48 -0
  17. package/dist/src/cli/commands/export.js.map +1 -0
  18. package/dist/src/cli/commands/job.d.ts +16 -0
  19. package/dist/src/cli/commands/job.js +350 -0
  20. package/dist/src/cli/commands/job.js.map +1 -0
  21. package/dist/src/cli/commands/logs.d.ts +3 -0
  22. package/dist/src/cli/commands/logs.js +44 -0
  23. package/dist/src/cli/commands/logs.js.map +1 -0
  24. package/dist/src/cli/commands/selection.d.ts +19 -0
  25. package/dist/src/cli/commands/selection.js +182 -0
  26. package/dist/src/cli/commands/selection.js.map +1 -0
  27. package/dist/src/cli/commands/settings.d.ts +1 -0
  28. package/dist/src/cli/commands/settings.js +31 -0
  29. package/dist/src/cli/commands/settings.js.map +1 -0
  30. package/dist/src/cli/commands/startup.d.ts +5 -0
  31. package/dist/src/cli/commands/startup.js +26 -0
  32. package/dist/src/cli/commands/startup.js.map +1 -0
  33. package/dist/src/cli/flows/jobAddFlow.d.ts +26 -0
  34. package/dist/src/cli/flows/jobAddFlow.js +209 -0
  35. package/dist/src/cli/flows/jobAddFlow.js.map +1 -0
  36. package/dist/src/cli/flows/settingsFlow.d.ts +15 -0
  37. package/dist/src/cli/flows/settingsFlow.js +180 -0
  38. package/dist/src/cli/flows/settingsFlow.js.map +1 -0
  39. package/dist/src/cli/flows/settingsFlowModel.d.ts +47 -0
  40. package/dist/src/cli/flows/settingsFlowModel.js +143 -0
  41. package/dist/src/cli/flows/settingsFlowModel.js.map +1 -0
  42. package/dist/src/cli/index.d.ts +2 -0
  43. package/dist/src/cli/index.js +138 -0
  44. package/dist/src/cli/index.js.map +1 -0
  45. package/dist/src/cli/ui/consoleUi.d.ts +13 -0
  46. package/dist/src/cli/ui/consoleUi.js +165 -0
  47. package/dist/src/cli/ui/consoleUi.js.map +1 -0
  48. package/dist/src/cli/ui/time.d.ts +9 -0
  49. package/dist/src/cli/ui/time.js +35 -0
  50. package/dist/src/cli/ui/time.js.map +1 -0
  51. package/dist/src/index.d.ts +1 -0
  52. package/dist/src/index.js +2 -0
  53. package/dist/src/index.js.map +1 -0
  54. package/dist/src/scripts/e2eSmoke.d.ts +1 -0
  55. package/dist/src/scripts/e2eSmoke.js +102 -0
  56. package/dist/src/scripts/e2eSmoke.js.map +1 -0
  57. package/dist/src/services/analytics/analyticsService.d.ts +50 -0
  58. package/dist/src/services/analytics/analyticsService.js +88 -0
  59. package/dist/src/services/analytics/analyticsService.js.map +1 -0
  60. package/dist/src/services/daemonControl.d.ts +12 -0
  61. package/dist/src/services/daemonControl.js +58 -0
  62. package/dist/src/services/daemonControl.js.map +1 -0
  63. package/dist/src/services/db/repositories/jobsRepo.d.ts +19 -0
  64. package/dist/src/services/db/repositories/jobsRepo.js +164 -0
  65. package/dist/src/services/db/repositories/jobsRepo.js.map +1 -0
  66. package/dist/src/services/db/repositories/runsRepo.d.ts +58 -0
  67. package/dist/src/services/db/repositories/runsRepo.js +190 -0
  68. package/dist/src/services/db/repositories/runsRepo.js.map +1 -0
  69. package/dist/src/services/db/repositories/scanItemsRepo.d.ts +69 -0
  70. package/dist/src/services/db/repositories/scanItemsRepo.js +176 -0
  71. package/dist/src/services/db/repositories/scanItemsRepo.js.map +1 -0
  72. package/dist/src/services/db/repositories/settingsRepo.d.ts +14 -0
  73. package/dist/src/services/db/repositories/settingsRepo.js +132 -0
  74. package/dist/src/services/db/repositories/settingsRepo.js.map +1 -0
  75. package/dist/src/services/db/sqlite.d.ts +2 -0
  76. package/dist/src/services/db/sqlite.js +192 -0
  77. package/dist/src/services/db/sqlite.js.map +1 -0
  78. package/dist/src/services/export/csvResults.d.ts +10 -0
  79. package/dist/src/services/export/csvResults.js +42 -0
  80. package/dist/src/services/export/csvResults.js.map +1 -0
  81. package/dist/src/services/logging/logReader.d.ts +4 -0
  82. package/dist/src/services/logging/logReader.js +230 -0
  83. package/dist/src/services/logging/logReader.js.map +1 -0
  84. package/dist/src/services/logging/logRotation.d.ts +1 -0
  85. package/dist/src/services/logging/logRotation.js +30 -0
  86. package/dist/src/services/logging/logRotation.js.map +1 -0
  87. package/dist/src/services/logging/runLogger.d.ts +9 -0
  88. package/dist/src/services/logging/runLogger.js +42 -0
  89. package/dist/src/services/logging/runLogger.js.map +1 -0
  90. package/dist/src/services/openrouter/client.d.ts +60 -0
  91. package/dist/src/services/openrouter/client.js +437 -0
  92. package/dist/src/services/openrouter/client.js.map +1 -0
  93. package/dist/src/services/openrouter/prompts.d.ts +5 -0
  94. package/dist/src/services/openrouter/prompts.js +48 -0
  95. package/dist/src/services/openrouter/prompts.js.map +1 -0
  96. package/dist/src/services/reddit/client.d.ts +25 -0
  97. package/dist/src/services/reddit/client.js +186 -0
  98. package/dist/src/services/reddit/client.js.map +1 -0
  99. package/dist/src/services/scheduler/cronScheduler.d.ts +11 -0
  100. package/dist/src/services/scheduler/cronScheduler.js +76 -0
  101. package/dist/src/services/scheduler/cronScheduler.js.map +1 -0
  102. package/dist/src/services/scheduler/jobRunner.d.ts +76 -0
  103. package/dist/src/services/scheduler/jobRunner.js +414 -0
  104. package/dist/src/services/scheduler/jobRunner.js.map +1 -0
  105. package/dist/src/services/scheduler/jobRunnerStub.d.ts +5 -0
  106. package/dist/src/services/scheduler/jobRunnerStub.js +11 -0
  107. package/dist/src/services/scheduler/jobRunnerStub.js.map +1 -0
  108. package/dist/src/services/security/secretStore.d.ts +6 -0
  109. package/dist/src/services/security/secretStore.js +193 -0
  110. package/dist/src/services/security/secretStore.js.map +1 -0
  111. package/dist/src/services/startup/index.d.ts +13 -0
  112. package/dist/src/services/startup/index.js +120 -0
  113. package/dist/src/services/startup/index.js.map +1 -0
  114. package/dist/src/services/startup/linuxCronFallback.d.ts +2 -0
  115. package/dist/src/services/startup/linuxCronFallback.js +29 -0
  116. package/dist/src/services/startup/linuxCronFallback.js.map +1 -0
  117. package/dist/src/services/startup/linuxSystemd.d.ts +3 -0
  118. package/dist/src/services/startup/linuxSystemd.js +47 -0
  119. package/dist/src/services/startup/linuxSystemd.js.map +1 -0
  120. package/dist/src/services/startup/macosLaunchd.d.ts +2 -0
  121. package/dist/src/services/startup/macosLaunchd.js +40 -0
  122. package/dist/src/services/startup/macosLaunchd.js.map +1 -0
  123. package/dist/src/services/startup/windowsRunFallback.d.ts +2 -0
  124. package/dist/src/services/startup/windowsRunFallback.js +17 -0
  125. package/dist/src/services/startup/windowsRunFallback.js.map +1 -0
  126. package/dist/src/services/startup/windowsTaskScheduler.d.ts +2 -0
  127. package/dist/src/services/startup/windowsTaskScheduler.js +16 -0
  128. package/dist/src/services/startup/windowsTaskScheduler.js.map +1 -0
  129. package/dist/src/types/job.d.ts +34 -0
  130. package/dist/src/types/job.js +2 -0
  131. package/dist/src/types/job.js.map +1 -0
  132. package/dist/src/types/settings.d.ts +35 -0
  133. package/dist/src/types/settings.js +8 -0
  134. package/dist/src/types/settings.js.map +1 -0
  135. package/dist/src/ui/components/AppFrame.d.ts +17 -0
  136. package/dist/src/ui/components/AppFrame.js +26 -0
  137. package/dist/src/ui/components/AppFrame.js.map +1 -0
  138. package/dist/src/ui/components/CliHeader.d.ts +8 -0
  139. package/dist/src/ui/components/CliHeader.js +8 -0
  140. package/dist/src/ui/components/CliHeader.js.map +1 -0
  141. package/dist/src/ui/components/SubredditMultiSelect.d.ts +7 -0
  142. package/dist/src/ui/components/SubredditMultiSelect.js +91 -0
  143. package/dist/src/ui/components/SubredditMultiSelect.js.map +1 -0
  144. package/dist/src/ui/components/TextPrompt.d.ts +10 -0
  145. package/dist/src/ui/components/TextPrompt.js +13 -0
  146. package/dist/src/ui/components/TextPrompt.js.map +1 -0
  147. package/dist/src/ui/components/YesNoSelector.d.ts +10 -0
  148. package/dist/src/ui/components/YesNoSelector.js +25 -0
  149. package/dist/src/ui/components/YesNoSelector.js.map +1 -0
  150. package/dist/src/ui/components/subredditOptions.d.ts +5 -0
  151. package/dist/src/ui/components/subredditOptions.js +14 -0
  152. package/dist/src/ui/components/subredditOptions.js.map +1 -0
  153. package/dist/src/ui/components/yesNoSelectorModel.d.ts +9 -0
  154. package/dist/src/ui/components/yesNoSelectorModel.js +23 -0
  155. package/dist/src/ui/components/yesNoSelectorModel.js.map +1 -0
  156. package/dist/src/ui/theme.d.ts +26 -0
  157. package/dist/src/ui/theme.js +37 -0
  158. package/dist/src/ui/theme.js.map +1 -0
  159. package/dist/src/utils/logger.d.ts +5 -0
  160. package/dist/src/utils/logger.js +15 -0
  161. package/dist/src/utils/logger.js.map +1 -0
  162. package/dist/src/utils/notify.d.ts +6 -0
  163. package/dist/src/utils/notify.js +14 -0
  164. package/dist/src/utils/notify.js.map +1 -0
  165. package/dist/src/utils/paths.d.ts +10 -0
  166. package/dist/src/utils/paths.js +24 -0
  167. package/dist/src/utils/paths.js.map +1 -0
  168. package/dist/src/utils/scanLogFormatting.d.ts +26 -0
  169. package/dist/src/utils/scanLogFormatting.js +60 -0
  170. package/dist/src/utils/scanLogFormatting.js.map +1 -0
  171. package/package.json +74 -0
@@ -0,0 +1,14 @@
1
+ import { type AppSettings, type RedditCredentials, type RedditCredentialState, type RedditCredentialsUpdate } from '../../../types/settings.js';
2
+ export declare class SettingsRepository {
3
+ private readonly db;
4
+ get(key: string): string | null;
5
+ set(key: string, value: string): void;
6
+ delete(key: string): void;
7
+ getAppSettings(): AppSettings;
8
+ setAppSettings(settings: AppSettings): void;
9
+ private getOrCreateRedditAppName;
10
+ private migrateLegacyRedditClientSecret;
11
+ getRedditCredentialState(): Promise<RedditCredentialState>;
12
+ getRedditCredentials(): Promise<RedditCredentials | null>;
13
+ setRedditCredentials(update: RedditCredentialsUpdate | RedditCredentials): Promise<void>;
14
+ }
@@ -0,0 +1,132 @@
1
+ import crypto from 'node:crypto';
2
+ import { getDb } from '../sqlite.js';
3
+ import { DEFAULT_MODEL, DEFAULT_CRON_INTERVAL_MINUTES, DEFAULT_JOB_TIMEOUT_MS } from '../../../types/settings.js';
4
+ import { getRedditClientSecret, setRedditClientSecret } from '../../security/secretStore.js';
5
+ const DEFAULT_MODEL_SETTINGS = {
6
+ temperature: 0.3,
7
+ maxTokens: 800,
8
+ topP: 1
9
+ };
10
+ function normalizeModel(model) {
11
+ const value = model?.trim();
12
+ return value && value.length > 0 ? value : DEFAULT_MODEL;
13
+ }
14
+ function generateDefaultRedditAppName() {
15
+ return `snoopy-${crypto.randomBytes(4).toString('hex')}`;
16
+ }
17
+ export class SettingsRepository {
18
+ db = getDb();
19
+ get(key) {
20
+ const row = this.db
21
+ .prepare('SELECT value FROM settings WHERE key = ?')
22
+ .get(key);
23
+ return row?.value ?? null;
24
+ }
25
+ set(key, value) {
26
+ this.db
27
+ .prepare(`INSERT INTO settings (key, value, updated_at)
28
+ VALUES (?, ?, datetime('now'))
29
+ ON CONFLICT(key) DO UPDATE SET
30
+ value = excluded.value,
31
+ updated_at = datetime('now')`)
32
+ .run(key, value);
33
+ }
34
+ delete(key) {
35
+ this.db.prepare('DELETE FROM settings WHERE key = ?').run(key);
36
+ }
37
+ getAppSettings() {
38
+ const rawModel = this.get('model');
39
+ const modelSettingsRaw = this.get('model_settings_json');
40
+ const parsed = modelSettingsRaw
41
+ ? JSON.parse(modelSettingsRaw)
42
+ : DEFAULT_MODEL_SETTINGS;
43
+ const rawCronInterval = this.get('cron_interval_minutes');
44
+ const cronIntervalMinutes = rawCronInterval
45
+ ? Math.max(1, Number(rawCronInterval) || DEFAULT_CRON_INTERVAL_MINUTES)
46
+ : DEFAULT_CRON_INTERVAL_MINUTES;
47
+ const rawJobTimeout = this.get('job_timeout_ms');
48
+ const jobTimeoutMs = rawJobTimeout
49
+ ? Math.max(0, Number(rawJobTimeout) || DEFAULT_JOB_TIMEOUT_MS)
50
+ : DEFAULT_JOB_TIMEOUT_MS;
51
+ const rawNotifications = this.get('notifications_enabled');
52
+ const notificationsEnabled = rawNotifications == null ? true : rawNotifications === 'true';
53
+ const settings = {
54
+ model: normalizeModel(rawModel),
55
+ modelSettings: {
56
+ ...DEFAULT_MODEL_SETTINGS,
57
+ ...parsed
58
+ },
59
+ cronIntervalMinutes,
60
+ jobTimeoutMs,
61
+ notificationsEnabled
62
+ };
63
+ if (rawModel !== settings.model || modelSettingsRaw === null) {
64
+ this.setAppSettings(settings);
65
+ }
66
+ return settings;
67
+ }
68
+ setAppSettings(settings) {
69
+ this.set('model', normalizeModel(settings.model));
70
+ this.set('model_settings_json', JSON.stringify({
71
+ ...DEFAULT_MODEL_SETTINGS,
72
+ ...settings.modelSettings
73
+ }));
74
+ this.set('cron_interval_minutes', String(Math.max(1, settings.cronIntervalMinutes ?? DEFAULT_CRON_INTERVAL_MINUTES)));
75
+ this.set('job_timeout_ms', String(Math.max(0, settings.jobTimeoutMs ?? DEFAULT_JOB_TIMEOUT_MS)));
76
+ this.set('notifications_enabled', settings.notificationsEnabled ? 'true' : 'false');
77
+ }
78
+ getOrCreateRedditAppName() {
79
+ const appName = this.get('reddit_app_name')?.trim() ?? '';
80
+ if (appName) {
81
+ return appName;
82
+ }
83
+ const generated = generateDefaultRedditAppName();
84
+ this.set('reddit_app_name', generated);
85
+ return generated;
86
+ }
87
+ async migrateLegacyRedditClientSecret() {
88
+ const legacySecret = this.get('reddit_client_secret')?.trim() ?? '';
89
+ if (!legacySecret) {
90
+ return;
91
+ }
92
+ const existingSecret = await getRedditClientSecret();
93
+ if (!existingSecret) {
94
+ await setRedditClientSecret(legacySecret);
95
+ }
96
+ this.delete('reddit_client_secret');
97
+ }
98
+ async getRedditCredentialState() {
99
+ await this.migrateLegacyRedditClientSecret();
100
+ const appName = this.getOrCreateRedditAppName();
101
+ const clientId = this.get('reddit_client_id')?.trim() ?? '';
102
+ const clientSecret = await getRedditClientSecret();
103
+ return {
104
+ appName,
105
+ clientId,
106
+ hasClientSecret: Boolean(clientSecret?.trim())
107
+ };
108
+ }
109
+ async getRedditCredentials() {
110
+ await this.migrateLegacyRedditClientSecret();
111
+ const appName = this.getOrCreateRedditAppName();
112
+ const clientId = this.get('reddit_client_id')?.trim() ?? '';
113
+ const clientSecret = (await getRedditClientSecret())?.trim() ?? '';
114
+ if (!clientId || !clientSecret) {
115
+ return null;
116
+ }
117
+ return {
118
+ appName,
119
+ clientId,
120
+ clientSecret
121
+ };
122
+ }
123
+ async setRedditCredentials(update) {
124
+ this.set('reddit_app_name', update.appName.trim());
125
+ this.set('reddit_client_id', update.clientId.trim());
126
+ const secret = update.clientSecret?.trim();
127
+ if (secret) {
128
+ await setRedditClientSecret(secret);
129
+ }
130
+ }
131
+ }
132
+ //# sourceMappingURL=settingsRepo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settingsRepo.js","sourceRoot":"","sources":["../../../../../src/services/db/repositories/settingsRepo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import Database from 'better-sqlite3';
2
+ export declare function getDb(): Database.Database;
@@ -0,0 +1,192 @@
1
+ import Database from 'better-sqlite3';
2
+ import { ensureAppDirs } from '../../utils/paths.js';
3
+ let db = null;
4
+ export function getDb() {
5
+ if (db) {
6
+ return db;
7
+ }
8
+ const paths = ensureAppDirs();
9
+ db = new Database(paths.dbPath);
10
+ try {
11
+ db.pragma('journal_mode = WAL');
12
+ }
13
+ catch {
14
+ // In rare concurrent startup cases (for example tests), DB may already be locked.
15
+ }
16
+ db.exec(`
17
+ CREATE TABLE IF NOT EXISTS settings (
18
+ key TEXT PRIMARY KEY,
19
+ value TEXT NOT NULL,
20
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
21
+ );
22
+
23
+ CREATE TABLE IF NOT EXISTS jobs (
24
+ id TEXT PRIMARY KEY,
25
+ slug TEXT UNIQUE,
26
+ name TEXT NOT NULL UNIQUE,
27
+ description TEXT NOT NULL,
28
+ qualification_prompt TEXT NOT NULL,
29
+ subreddits_json TEXT NOT NULL,
30
+ schedule_cron TEXT NOT NULL DEFAULT '*/30 * * * *',
31
+ enabled INTEGER NOT NULL DEFAULT 1,
32
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
33
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
34
+ );
35
+
36
+ CREATE TABLE IF NOT EXISTS job_runs (
37
+ id TEXT PRIMARY KEY,
38
+ job_id TEXT NOT NULL,
39
+ status TEXT NOT NULL,
40
+ message TEXT,
41
+ started_at TEXT,
42
+ finished_at TEXT,
43
+ items_discovered INTEGER NOT NULL DEFAULT 0,
44
+ items_new INTEGER NOT NULL DEFAULT 0,
45
+ items_qualified INTEGER NOT NULL DEFAULT 0,
46
+ prompt_tokens INTEGER NOT NULL DEFAULT 0,
47
+ completion_tokens INTEGER NOT NULL DEFAULT 0,
48
+ estimated_cost_usd REAL,
49
+ log_file_path TEXT,
50
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
51
+ FOREIGN KEY (job_id) REFERENCES jobs(id)
52
+ );
53
+
54
+ CREATE TABLE IF NOT EXISTS scan_items (
55
+ id TEXT PRIMARY KEY,
56
+ job_id TEXT NOT NULL,
57
+ run_id TEXT NOT NULL,
58
+ type TEXT NOT NULL CHECK (type IN ('post', 'comment')),
59
+ reddit_post_id TEXT NOT NULL,
60
+ reddit_comment_id TEXT,
61
+ subreddit TEXT NOT NULL,
62
+ author TEXT NOT NULL,
63
+ title TEXT,
64
+ body TEXT NOT NULL,
65
+ url TEXT NOT NULL,
66
+ reddit_posted_at TEXT NOT NULL,
67
+ qualified INTEGER NOT NULL DEFAULT 0,
68
+ viewed INTEGER NOT NULL DEFAULT 0,
69
+ validated INTEGER NOT NULL DEFAULT 0,
70
+ processed INTEGER NOT NULL DEFAULT 0,
71
+ prompt_tokens INTEGER NOT NULL DEFAULT 0,
72
+ completion_tokens INTEGER NOT NULL DEFAULT 0,
73
+ estimated_cost_usd REAL,
74
+ qualification_reason TEXT,
75
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
76
+ FOREIGN KEY (job_id) REFERENCES jobs(id),
77
+ FOREIGN KEY (run_id) REFERENCES job_runs(id)
78
+ );
79
+
80
+ CREATE TABLE IF NOT EXISTS daemon_state (
81
+ id INTEGER PRIMARY KEY CHECK (id = 1),
82
+ is_running INTEGER NOT NULL,
83
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
84
+ );
85
+ `);
86
+ try {
87
+ db.exec('ALTER TABLE jobs ADD COLUMN slug TEXT');
88
+ }
89
+ catch {
90
+ // Column already exists.
91
+ }
92
+ try {
93
+ db.exec('ALTER TABLE jobs ADD COLUMN monitor_comments INTEGER NOT NULL DEFAULT 1');
94
+ }
95
+ catch {
96
+ // Column already exists.
97
+ }
98
+ db.exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_jobs_slug ON jobs(slug)');
99
+ db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_scan_items_dedup ON scan_items(job_id, reddit_post_id, COALESCE(reddit_comment_id, ''))");
100
+ try {
101
+ db.exec('ALTER TABLE job_runs ADD COLUMN started_at TEXT');
102
+ }
103
+ catch {
104
+ // Column already exists.
105
+ }
106
+ try {
107
+ db.exec('ALTER TABLE job_runs ADD COLUMN finished_at TEXT');
108
+ }
109
+ catch {
110
+ // Column already exists.
111
+ }
112
+ try {
113
+ db.exec('ALTER TABLE job_runs ADD COLUMN items_discovered INTEGER NOT NULL DEFAULT 0');
114
+ }
115
+ catch {
116
+ // Column already exists.
117
+ }
118
+ try {
119
+ db.exec('ALTER TABLE job_runs ADD COLUMN items_new INTEGER NOT NULL DEFAULT 0');
120
+ }
121
+ catch {
122
+ // Column already exists.
123
+ }
124
+ try {
125
+ db.exec('ALTER TABLE job_runs ADD COLUMN items_qualified INTEGER NOT NULL DEFAULT 0');
126
+ }
127
+ catch {
128
+ // Column already exists.
129
+ }
130
+ try {
131
+ db.exec('ALTER TABLE job_runs ADD COLUMN prompt_tokens INTEGER NOT NULL DEFAULT 0');
132
+ }
133
+ catch {
134
+ // Column already exists.
135
+ }
136
+ try {
137
+ db.exec('ALTER TABLE job_runs ADD COLUMN completion_tokens INTEGER NOT NULL DEFAULT 0');
138
+ }
139
+ catch {
140
+ // Column already exists.
141
+ }
142
+ try {
143
+ db.exec('ALTER TABLE job_runs ADD COLUMN estimated_cost_usd REAL');
144
+ }
145
+ catch {
146
+ // Column already exists.
147
+ }
148
+ try {
149
+ db.exec('ALTER TABLE job_runs ADD COLUMN log_file_path TEXT');
150
+ }
151
+ catch {
152
+ // Column already exists.
153
+ }
154
+ try {
155
+ db.exec('ALTER TABLE scan_items ADD COLUMN viewed INTEGER NOT NULL DEFAULT 0');
156
+ }
157
+ catch {
158
+ // Column already exists.
159
+ }
160
+ try {
161
+ db.exec('ALTER TABLE scan_items ADD COLUMN validated INTEGER NOT NULL DEFAULT 0');
162
+ }
163
+ catch {
164
+ // Column already exists.
165
+ }
166
+ try {
167
+ db.exec('ALTER TABLE scan_items ADD COLUMN processed INTEGER NOT NULL DEFAULT 0');
168
+ }
169
+ catch {
170
+ // Column already exists.
171
+ }
172
+ try {
173
+ db.exec('ALTER TABLE scan_items ADD COLUMN prompt_tokens INTEGER NOT NULL DEFAULT 0');
174
+ }
175
+ catch {
176
+ // Column already exists.
177
+ }
178
+ try {
179
+ db.exec('ALTER TABLE scan_items ADD COLUMN completion_tokens INTEGER NOT NULL DEFAULT 0');
180
+ }
181
+ catch {
182
+ // Column already exists.
183
+ }
184
+ try {
185
+ db.exec('ALTER TABLE scan_items ADD COLUMN estimated_cost_usd REAL');
186
+ }
187
+ catch {
188
+ // Column already exists.
189
+ }
190
+ return db;
191
+ }
192
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../../../src/services/db/sqlite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Job } from '../../types/job.js';
2
+ import type { QualifiedScanItemRow } from '../db/repositories/scanItemsRepo.js';
3
+ export interface CsvExportSummary {
4
+ outputPath: string;
5
+ rowCount: number;
6
+ }
7
+ export declare class CsvResultsExporter {
8
+ private readonly resultsDir;
9
+ exportJobResults(job: Job, qualifiedRows: QualifiedScanItemRow[]): CsvExportSummary;
10
+ }
@@ -0,0 +1,42 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getAppPaths } from '../../utils/paths.js';
4
+ const CSV_HEADERS = ['URL', 'Title', 'Truncated Content', 'Author', 'Justification', 'Date'];
5
+ function truncateContent(value, maxLength) {
6
+ if (value.length <= maxLength) {
7
+ return value;
8
+ }
9
+ return `${value.slice(0, maxLength)}...`;
10
+ }
11
+ function csvEscape(value) {
12
+ if (value.includes(',') || value.includes('"') || value.includes('\n') || value.includes('\r')) {
13
+ return `"${value.replace(/"/g, '""')}"`;
14
+ }
15
+ return value;
16
+ }
17
+ function toCsvLine(values) {
18
+ return values.map(csvEscape).join(',');
19
+ }
20
+ export class CsvResultsExporter {
21
+ resultsDir = getAppPaths().resultsDir;
22
+ exportJobResults(job, qualifiedRows) {
23
+ const outputPath = path.join(this.resultsDir, `${job.slug}.csv`);
24
+ const lines = [toCsvLine([...CSV_HEADERS])];
25
+ for (const row of qualifiedRows) {
26
+ lines.push(toCsvLine([
27
+ row.url,
28
+ row.title ?? '',
29
+ truncateContent(row.body, 300),
30
+ row.author,
31
+ row.qualificationReason ?? '',
32
+ row.redditPostedAt
33
+ ]));
34
+ }
35
+ fs.writeFileSync(outputPath, `${lines.join('\n')}\n`, 'utf8');
36
+ return {
37
+ outputPath,
38
+ rowCount: qualifiedRows.length
39
+ };
40
+ }
41
+ }
42
+ //# sourceMappingURL=csvResults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csvResults.js","sourceRoot":"","sources":["../../../../src/services/export/csvResults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAU,CAAC;AAEtG,SAAS,eAAe,CAAC,KAAa,EAAE,SAAiB;IACvD,IAAI,KAAK,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,KAAK,CAAC;AAC3C,CAAC;AAED,SAAS,SAAS,CAAC,KAAa;IAC9B,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/F,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;IAC1C,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,SAAS,CAAC,MAAgB;IACjC,OAAO,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAOD,MAAM,OAAO,kBAAkB;IACZ,UAAU,GAAG,WAAW,EAAE,CAAC,UAAU,CAAC;IAEvD,gBAAgB,CAAC,GAAQ,EAAE,aAAqC;QAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,CAAC,SAAS,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAE5C,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;YAChC,KAAK,CAAC,IAAI,CACR,SAAS,CAAC;gBACR,GAAG,CAAC,GAAG;gBACP,GAAG,CAAC,KAAK,IAAI,EAAE;gBACf,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC;gBAC9B,GAAG,CAAC,MAAM;gBACV,GAAG,CAAC,mBAAmB,IAAI,EAAE;gBAC7B,GAAG,CAAC,cAAc;aACnB,CAAC,CACH,CAAC;QACJ,CAAC;QAED,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAE9D,OAAO;YACL,UAAU;YACV,QAAQ,EAAE,aAAa,CAAC,MAAM;SAC/B,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ export declare function formatRunLogPretty(logContent: string): string;
2
+ export declare function readRunLog(logFilePath: string | null | undefined): string | null;
3
+ export declare function extractErrorEntries(logContent: string): string[];
4
+ export declare function hasErrorEntries(logContent: string | null): boolean;
@@ -0,0 +1,230 @@
1
+ import fs from 'node:fs';
2
+ import { formatCommentScanLine, formatPostScanLine, toSnippet } from '../../utils/scanLogFormatting.js';
3
+ const LOG_ENTRY_START = /^\[[^\]]+\] \[[^\]]+\]/;
4
+ const ERROR_ENTRY_START = /^\[[^\]]+\] \[ERROR\]/;
5
+ const LOG_HEADER_PATTERN = /^\[([^\]]+)\] \[([^\]]+)\]\s?(.*)$/;
6
+ function parseLogEntries(logContent) {
7
+ const entries = [];
8
+ let current = null;
9
+ for (const line of logContent.split('\n')) {
10
+ const match = line.match(LOG_HEADER_PATTERN);
11
+ if (match) {
12
+ if (current) {
13
+ entries.push(current);
14
+ }
15
+ current = {
16
+ timestamp: match[1] ?? '',
17
+ level: match[2] ?? 'INFO',
18
+ message: match[3] ?? ''
19
+ };
20
+ continue;
21
+ }
22
+ if (!current) {
23
+ continue;
24
+ }
25
+ current.message = current.message ? `${current.message}\n${line}` : line;
26
+ }
27
+ if (current) {
28
+ entries.push(current);
29
+ }
30
+ return entries;
31
+ }
32
+ function parseJsonObject(message) {
33
+ const trimmed = message.trim();
34
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
35
+ return null;
36
+ }
37
+ try {
38
+ const parsed = JSON.parse(trimmed);
39
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
40
+ return null;
41
+ }
42
+ return parsed;
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ function toRecord(value) {
49
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
50
+ return null;
51
+ }
52
+ return value;
53
+ }
54
+ function getStringField(input, key) {
55
+ const value = input[key];
56
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
57
+ }
58
+ function getBooleanField(input, key) {
59
+ const value = input[key];
60
+ return typeof value === 'boolean' ? value : undefined;
61
+ }
62
+ function buildCommentKey(postId, commentId) {
63
+ return `${postId}::${commentId}`;
64
+ }
65
+ export function formatRunLogPretty(logContent) {
66
+ const entries = parseLogEntries(logContent);
67
+ if (entries.length === 0) {
68
+ return logContent;
69
+ }
70
+ const lines = [];
71
+ const postContexts = new Map();
72
+ const commentContexts = new Map();
73
+ for (const entry of entries) {
74
+ const payload = parseJsonObject(entry.message);
75
+ if (!payload) {
76
+ if (entry.level === 'ERROR' || entry.level === 'WARN') {
77
+ lines.push(`${entry.timestamp} [${entry.level}] ${entry.message.trim()}`);
78
+ }
79
+ continue;
80
+ }
81
+ const eventName = getStringField(payload, 'event');
82
+ if (!eventName) {
83
+ continue;
84
+ }
85
+ switch (eventName) {
86
+ case 'run_start': {
87
+ const jobName = getStringField(payload, 'jobName') ?? getStringField(payload, 'jobId') ?? 'unknown job';
88
+ lines.push(`${entry.timestamp} Run started for ${jobName}`);
89
+ break;
90
+ }
91
+ case 'subreddit_fetched': {
92
+ const subreddit = getStringField(payload, 'subreddit') ?? '?';
93
+ const postCount = payload.postCount;
94
+ const count = typeof postCount === 'number' ? postCount : '?';
95
+ lines.push(`${entry.timestamp} Fetched r/${subreddit}: ${count} posts`);
96
+ break;
97
+ }
98
+ case 'post_qualify_start': {
99
+ const postId = getStringField(payload, 'postId');
100
+ if (!postId) {
101
+ break;
102
+ }
103
+ postContexts.set(postId, {
104
+ title: getStringField(payload, 'title'),
105
+ bodySnippet: toSnippet(getStringField(payload, 'body')),
106
+ postUrl: getStringField(payload, 'url')
107
+ });
108
+ break;
109
+ }
110
+ case 'post_qualify_result': {
111
+ const postId = getStringField(payload, 'postId');
112
+ if (!postId) {
113
+ break;
114
+ }
115
+ const result = toRecord(payload.result);
116
+ const context = postContexts.get(postId);
117
+ lines.push(`${entry.timestamp} ${formatPostScanLine({
118
+ postId,
119
+ title: context?.title,
120
+ bodySnippet: context?.bodySnippet,
121
+ qualified: result ? getBooleanField(result, 'qualified') : undefined,
122
+ qualificationReason: result ? getStringField(result, 'reason') : undefined,
123
+ postUrl: context?.postUrl
124
+ })}`);
125
+ break;
126
+ }
127
+ case 'comment_qualify_start': {
128
+ const postId = getStringField(payload, 'postId');
129
+ const commentId = getStringField(payload, 'commentId');
130
+ const author = getStringField(payload, 'author') ?? '[unknown]';
131
+ if (!postId || !commentId) {
132
+ break;
133
+ }
134
+ commentContexts.set(buildCommentKey(postId, commentId), {
135
+ postId,
136
+ commentId,
137
+ author,
138
+ commentSnippet: toSnippet(getStringField(payload, 'commentBody')),
139
+ postUrl: getStringField(payload, 'postUrl'),
140
+ commentUrl: getStringField(payload, 'commentUrl')
141
+ });
142
+ break;
143
+ }
144
+ case 'comment_qualify_result': {
145
+ const postId = getStringField(payload, 'postId');
146
+ const commentId = getStringField(payload, 'commentId');
147
+ const fallbackAuthor = getStringField(payload, 'author') ?? '[unknown]';
148
+ if (!postId || !commentId) {
149
+ break;
150
+ }
151
+ const result = toRecord(payload.result);
152
+ const context = commentContexts.get(buildCommentKey(postId, commentId));
153
+ lines.push(`${entry.timestamp} ${formatCommentScanLine({
154
+ postId,
155
+ commentId,
156
+ author: context?.author ?? fallbackAuthor,
157
+ commentSnippet: context?.commentSnippet,
158
+ qualified: result ? getBooleanField(result, 'qualified') : undefined,
159
+ qualificationReason: result ? getStringField(result, 'reason') : undefined,
160
+ postUrl: context?.postUrl,
161
+ commentUrl: context?.commentUrl
162
+ })}`);
163
+ break;
164
+ }
165
+ case 'run_complete': {
166
+ const stats = toRecord(payload.stats);
167
+ const discovered = stats?.itemsDiscovered;
168
+ const fresh = stats?.itemsNew;
169
+ const qualified = stats?.itemsQualified;
170
+ lines.push(`${entry.timestamp} Scan complete (discovered=${String(discovered ?? '?')}, new=${String(fresh ?? '?')}, qualified=${String(qualified ?? '?')})`);
171
+ break;
172
+ }
173
+ case 'run_failed': {
174
+ const message = getStringField(payload, 'message') ?? 'Unknown error';
175
+ lines.push(`${entry.timestamp} [ERROR] Run failed: ${message}`);
176
+ break;
177
+ }
178
+ default:
179
+ break;
180
+ }
181
+ }
182
+ if (lines.length === 0) {
183
+ return logContent;
184
+ }
185
+ return `${lines.join('\n')}\n`;
186
+ }
187
+ export function readRunLog(logFilePath) {
188
+ if (!logFilePath || !fs.existsSync(logFilePath)) {
189
+ return null;
190
+ }
191
+ return fs.readFileSync(logFilePath, 'utf8');
192
+ }
193
+ export function extractErrorEntries(logContent) {
194
+ const lines = logContent.split('\n');
195
+ const entries = [];
196
+ let current = [];
197
+ let inErrorEntry = false;
198
+ for (const line of lines) {
199
+ if (ERROR_ENTRY_START.test(line)) {
200
+ if (current.length > 0) {
201
+ entries.push(current.join('\n').trimEnd());
202
+ }
203
+ current = [line];
204
+ inErrorEntry = true;
205
+ continue;
206
+ }
207
+ if (LOG_ENTRY_START.test(line)) {
208
+ if (current.length > 0) {
209
+ entries.push(current.join('\n').trimEnd());
210
+ }
211
+ current = [];
212
+ inErrorEntry = false;
213
+ continue;
214
+ }
215
+ if (inErrorEntry) {
216
+ current.push(line);
217
+ }
218
+ }
219
+ if (current.length > 0) {
220
+ entries.push(current.join('\n').trimEnd());
221
+ }
222
+ return entries.filter((entry) => entry.length > 0);
223
+ }
224
+ export function hasErrorEntries(logContent) {
225
+ if (!logContent) {
226
+ return false;
227
+ }
228
+ return extractErrorEntries(logContent).length > 0;
229
+ }
230
+ //# sourceMappingURL=logReader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logReader.js","sourceRoot":"","sources":["../../../../src/services/logging/logReader.ts"],"names":[],"mappings":"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"}