@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.
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/dist/src/cli/commands/analytics.d.ts +3 -0
- package/dist/src/cli/commands/analytics.js +147 -0
- package/dist/src/cli/commands/analytics.js.map +1 -0
- package/dist/src/cli/commands/daemon.d.ts +5 -0
- package/dist/src/cli/commands/daemon.js +85 -0
- package/dist/src/cli/commands/daemon.js.map +1 -0
- package/dist/src/cli/commands/doctor.d.ts +1 -0
- package/dist/src/cli/commands/doctor.js +106 -0
- package/dist/src/cli/commands/doctor.js.map +1 -0
- package/dist/src/cli/commands/errors.d.ts +3 -0
- package/dist/src/cli/commands/errors.js +51 -0
- package/dist/src/cli/commands/errors.js.map +1 -0
- package/dist/src/cli/commands/export.d.ts +1 -0
- package/dist/src/cli/commands/export.js +48 -0
- package/dist/src/cli/commands/export.js.map +1 -0
- package/dist/src/cli/commands/job.d.ts +16 -0
- package/dist/src/cli/commands/job.js +350 -0
- package/dist/src/cli/commands/job.js.map +1 -0
- package/dist/src/cli/commands/logs.d.ts +3 -0
- package/dist/src/cli/commands/logs.js +44 -0
- package/dist/src/cli/commands/logs.js.map +1 -0
- package/dist/src/cli/commands/selection.d.ts +19 -0
- package/dist/src/cli/commands/selection.js +182 -0
- package/dist/src/cli/commands/selection.js.map +1 -0
- package/dist/src/cli/commands/settings.d.ts +1 -0
- package/dist/src/cli/commands/settings.js +31 -0
- package/dist/src/cli/commands/settings.js.map +1 -0
- package/dist/src/cli/commands/startup.d.ts +5 -0
- package/dist/src/cli/commands/startup.js +26 -0
- package/dist/src/cli/commands/startup.js.map +1 -0
- package/dist/src/cli/flows/jobAddFlow.d.ts +26 -0
- package/dist/src/cli/flows/jobAddFlow.js +209 -0
- package/dist/src/cli/flows/jobAddFlow.js.map +1 -0
- package/dist/src/cli/flows/settingsFlow.d.ts +15 -0
- package/dist/src/cli/flows/settingsFlow.js +180 -0
- package/dist/src/cli/flows/settingsFlow.js.map +1 -0
- package/dist/src/cli/flows/settingsFlowModel.d.ts +47 -0
- package/dist/src/cli/flows/settingsFlowModel.js +143 -0
- package/dist/src/cli/flows/settingsFlowModel.js.map +1 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +138 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/ui/consoleUi.d.ts +13 -0
- package/dist/src/cli/ui/consoleUi.js +165 -0
- package/dist/src/cli/ui/consoleUi.js.map +1 -0
- package/dist/src/cli/ui/time.d.ts +9 -0
- package/dist/src/cli/ui/time.js +35 -0
- package/dist/src/cli/ui/time.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/scripts/e2eSmoke.d.ts +1 -0
- package/dist/src/scripts/e2eSmoke.js +102 -0
- package/dist/src/scripts/e2eSmoke.js.map +1 -0
- package/dist/src/services/analytics/analyticsService.d.ts +50 -0
- package/dist/src/services/analytics/analyticsService.js +88 -0
- package/dist/src/services/analytics/analyticsService.js.map +1 -0
- package/dist/src/services/daemonControl.d.ts +12 -0
- package/dist/src/services/daemonControl.js +58 -0
- package/dist/src/services/daemonControl.js.map +1 -0
- package/dist/src/services/db/repositories/jobsRepo.d.ts +19 -0
- package/dist/src/services/db/repositories/jobsRepo.js +164 -0
- package/dist/src/services/db/repositories/jobsRepo.js.map +1 -0
- package/dist/src/services/db/repositories/runsRepo.d.ts +58 -0
- package/dist/src/services/db/repositories/runsRepo.js +190 -0
- package/dist/src/services/db/repositories/runsRepo.js.map +1 -0
- package/dist/src/services/db/repositories/scanItemsRepo.d.ts +69 -0
- package/dist/src/services/db/repositories/scanItemsRepo.js +176 -0
- package/dist/src/services/db/repositories/scanItemsRepo.js.map +1 -0
- package/dist/src/services/db/repositories/settingsRepo.d.ts +14 -0
- package/dist/src/services/db/repositories/settingsRepo.js +132 -0
- package/dist/src/services/db/repositories/settingsRepo.js.map +1 -0
- package/dist/src/services/db/sqlite.d.ts +2 -0
- package/dist/src/services/db/sqlite.js +192 -0
- package/dist/src/services/db/sqlite.js.map +1 -0
- package/dist/src/services/export/csvResults.d.ts +10 -0
- package/dist/src/services/export/csvResults.js +42 -0
- package/dist/src/services/export/csvResults.js.map +1 -0
- package/dist/src/services/logging/logReader.d.ts +4 -0
- package/dist/src/services/logging/logReader.js +230 -0
- package/dist/src/services/logging/logReader.js.map +1 -0
- package/dist/src/services/logging/logRotation.d.ts +1 -0
- package/dist/src/services/logging/logRotation.js +30 -0
- package/dist/src/services/logging/logRotation.js.map +1 -0
- package/dist/src/services/logging/runLogger.d.ts +9 -0
- package/dist/src/services/logging/runLogger.js +42 -0
- package/dist/src/services/logging/runLogger.js.map +1 -0
- package/dist/src/services/openrouter/client.d.ts +60 -0
- package/dist/src/services/openrouter/client.js +437 -0
- package/dist/src/services/openrouter/client.js.map +1 -0
- package/dist/src/services/openrouter/prompts.d.ts +5 -0
- package/dist/src/services/openrouter/prompts.js +48 -0
- package/dist/src/services/openrouter/prompts.js.map +1 -0
- package/dist/src/services/reddit/client.d.ts +25 -0
- package/dist/src/services/reddit/client.js +186 -0
- package/dist/src/services/reddit/client.js.map +1 -0
- package/dist/src/services/scheduler/cronScheduler.d.ts +11 -0
- package/dist/src/services/scheduler/cronScheduler.js +76 -0
- package/dist/src/services/scheduler/cronScheduler.js.map +1 -0
- package/dist/src/services/scheduler/jobRunner.d.ts +76 -0
- package/dist/src/services/scheduler/jobRunner.js +414 -0
- package/dist/src/services/scheduler/jobRunner.js.map +1 -0
- package/dist/src/services/scheduler/jobRunnerStub.d.ts +5 -0
- package/dist/src/services/scheduler/jobRunnerStub.js +11 -0
- package/dist/src/services/scheduler/jobRunnerStub.js.map +1 -0
- package/dist/src/services/security/secretStore.d.ts +6 -0
- package/dist/src/services/security/secretStore.js +193 -0
- package/dist/src/services/security/secretStore.js.map +1 -0
- package/dist/src/services/startup/index.d.ts +13 -0
- package/dist/src/services/startup/index.js +120 -0
- package/dist/src/services/startup/index.js.map +1 -0
- package/dist/src/services/startup/linuxCronFallback.d.ts +2 -0
- package/dist/src/services/startup/linuxCronFallback.js +29 -0
- package/dist/src/services/startup/linuxCronFallback.js.map +1 -0
- package/dist/src/services/startup/linuxSystemd.d.ts +3 -0
- package/dist/src/services/startup/linuxSystemd.js +47 -0
- package/dist/src/services/startup/linuxSystemd.js.map +1 -0
- package/dist/src/services/startup/macosLaunchd.d.ts +2 -0
- package/dist/src/services/startup/macosLaunchd.js +40 -0
- package/dist/src/services/startup/macosLaunchd.js.map +1 -0
- package/dist/src/services/startup/windowsRunFallback.d.ts +2 -0
- package/dist/src/services/startup/windowsRunFallback.js +17 -0
- package/dist/src/services/startup/windowsRunFallback.js.map +1 -0
- package/dist/src/services/startup/windowsTaskScheduler.d.ts +2 -0
- package/dist/src/services/startup/windowsTaskScheduler.js +16 -0
- package/dist/src/services/startup/windowsTaskScheduler.js.map +1 -0
- package/dist/src/types/job.d.ts +34 -0
- package/dist/src/types/job.js +2 -0
- package/dist/src/types/job.js.map +1 -0
- package/dist/src/types/settings.d.ts +35 -0
- package/dist/src/types/settings.js +8 -0
- package/dist/src/types/settings.js.map +1 -0
- package/dist/src/ui/components/AppFrame.d.ts +17 -0
- package/dist/src/ui/components/AppFrame.js +26 -0
- package/dist/src/ui/components/AppFrame.js.map +1 -0
- package/dist/src/ui/components/CliHeader.d.ts +8 -0
- package/dist/src/ui/components/CliHeader.js +8 -0
- package/dist/src/ui/components/CliHeader.js.map +1 -0
- package/dist/src/ui/components/SubredditMultiSelect.d.ts +7 -0
- package/dist/src/ui/components/SubredditMultiSelect.js +91 -0
- package/dist/src/ui/components/SubredditMultiSelect.js.map +1 -0
- package/dist/src/ui/components/TextPrompt.d.ts +10 -0
- package/dist/src/ui/components/TextPrompt.js +13 -0
- package/dist/src/ui/components/TextPrompt.js.map +1 -0
- package/dist/src/ui/components/YesNoSelector.d.ts +10 -0
- package/dist/src/ui/components/YesNoSelector.js +25 -0
- package/dist/src/ui/components/YesNoSelector.js.map +1 -0
- package/dist/src/ui/components/subredditOptions.d.ts +5 -0
- package/dist/src/ui/components/subredditOptions.js +14 -0
- package/dist/src/ui/components/subredditOptions.js.map +1 -0
- package/dist/src/ui/components/yesNoSelectorModel.d.ts +9 -0
- package/dist/src/ui/components/yesNoSelectorModel.js +23 -0
- package/dist/src/ui/components/yesNoSelectorModel.js.map +1 -0
- package/dist/src/ui/theme.d.ts +26 -0
- package/dist/src/ui/theme.js +37 -0
- package/dist/src/ui/theme.js.map +1 -0
- package/dist/src/utils/logger.d.ts +5 -0
- package/dist/src/utils/logger.js +15 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/notify.d.ts +6 -0
- package/dist/src/utils/notify.js +14 -0
- package/dist/src/utils/notify.js.map +1 -0
- package/dist/src/utils/paths.d.ts +10 -0
- package/dist/src/utils/paths.js +24 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/utils/scanLogFormatting.d.ts +26 -0
- package/dist/src/utils/scanLogFormatting.js +60 -0
- package/dist/src/utils/scanLogFormatting.js.map +1 -0
- package/package.json +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 snoopy contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
```text
|
|
2
|
+
┌─┐┌┐┌┌─┐┌─┐┌─┐┬ ┬
|
|
3
|
+
└─┐││││ ││ │├─┘└┬┘
|
|
4
|
+
└─┘┘└┘└─┘└─┘┴ ┴
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
# Monitor Reddit Conversations With AI
|
|
8
|
+
|
|
9
|
+
[](https://github.com/telepat-io/snoopy/actions/workflows/ci.yml)
|
|
10
|
+
[](#development)
|
|
11
|
+
[](https://www.npmjs.com/package/@telepat/snoopy)
|
|
12
|
+
|
|
13
|
+
📖 [Full documentation](https://docs.telepat.io/ideon/)
|
|
14
|
+
|
|
15
|
+
Snoopy helps you monitor Reddit for high-intent conversations that match your business goals.
|
|
16
|
+
|
|
17
|
+
Define what you care about in plain language, let Snoopy create a monitoring job, and continuously scan and qualify posts/comments so you can focus on response and outreach.
|
|
18
|
+
|
|
19
|
+
## Why Use Snoopy
|
|
20
|
+
|
|
21
|
+
- Turn broad Reddit traffic into a focused stream of opportunities.
|
|
22
|
+
- Define qualification logic once, then run continuously.
|
|
23
|
+
- Trigger manual runs when you want quick validation.
|
|
24
|
+
- Track run analytics (discovered/new/qualified items, token usage, cost estimate).
|
|
25
|
+
- Run cross-platform with startup-on-reboot support.
|
|
26
|
+
|
|
27
|
+
## What It Does
|
|
28
|
+
|
|
29
|
+
- Interactive job creation flow from natural-language criteria.
|
|
30
|
+
- AI-assisted clarification and job spec generation.
|
|
31
|
+
- Qualification against your prompt for posts (and comments when enabled).
|
|
32
|
+
- Local SQLite persistence for jobs, runs, and scan items.
|
|
33
|
+
- Built-in daemon for scheduled scanning (cron expressions).
|
|
34
|
+
- On-demand CSV export of qualified results per job.
|
|
35
|
+
- Startup registration for macOS, Linux, and Windows.
|
|
36
|
+
- Health checks via the doctor command.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
Requirements:
|
|
41
|
+
- Node.js 20+
|
|
42
|
+
- npm 10+
|
|
43
|
+
|
|
44
|
+
From npm:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install -g @telepat/snoopy
|
|
48
|
+
snoopy --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
From source:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install
|
|
55
|
+
npm run build
|
|
56
|
+
npm link
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For first-time onboarding (OpenRouter key setup, first `job add`, and verification), see [Installation & Setup](docs/installation-and-setup.md).
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
Run without a global install (contributors):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm run dev -- --help
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Core validation commands:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm run lint
|
|
73
|
+
npm run build
|
|
74
|
+
npm test
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
To refresh coverage locally:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm test -- --coverage
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Releases
|
|
84
|
+
|
|
85
|
+
Versioning and changelogs are managed automatically by [release-please](https://github.com/googleapis/release-please).
|
|
86
|
+
|
|
87
|
+
**How it works:**
|
|
88
|
+
1. Merge commits to `main` following [Conventional Commits](https://www.conventionalcommits.org/) (`fix:`, `feat:`, `feat!:`, etc.).
|
|
89
|
+
2. release-please maintains an open "Release PR" that accumulates version bumps and CHANGELOG entries.
|
|
90
|
+
3. Merge the Release PR to cut a release: `package.json` version is bumped, `CHANGELOG.md` is updated, a git tag is created, and the package is published to npm automatically.
|
|
91
|
+
|
|
92
|
+
**Commit types and semver mapping (while version < 1.0.0):**
|
|
93
|
+
- `fix:` → patch bump
|
|
94
|
+
- `feat:` → patch bump (minor bump is suppressed pre-1.0)
|
|
95
|
+
- `feat!:` or `fix!:` (breaking change) → minor bump (major bump is suppressed pre-1.0)
|
|
96
|
+
|
|
97
|
+
No manual `git tag` or `npm version` steps are needed.
|
|
98
|
+
|
|
99
|
+
## Quick Start
|
|
100
|
+
|
|
101
|
+
Note:
|
|
102
|
+
- Snoopy uses Reddit public JSON endpoints by default.
|
|
103
|
+
- Optional Reddit OAuth fallback credentials can be configured in `snoopy settings` for environments where unauthenticated access is blocked.
|
|
104
|
+
- `snoopy settings` shows a full settings menu so you can jump directly to any setting and save once.
|
|
105
|
+
|
|
106
|
+
1. Start interactive setup and create your first job:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
snoopy job add
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`job add` now runs an immediate first scan after saving the job so you can validate results right away.
|
|
113
|
+
Snoopy pauses scheduled scans for that new job during this first run attempt, then enables scheduling when it ends (including interruption/failure cases).
|
|
114
|
+
|
|
115
|
+
2. List jobs:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
snoopy jobs list
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
3. Run one job immediately (limit to 5 new items while testing):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
snoopy job run --limit 5
|
|
125
|
+
snoopy job run <jobRef> --limit 5
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
If `<jobRef>` is omitted for `job run`, `job enable`, `job disable`, `job delete`, `start`, `stop`, or `errors`, Snoopy shows your job list and lets you pick with up/down arrows and Enter.
|
|
129
|
+
|
|
130
|
+
4. View run history:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
snoopy job runs <jobRef>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
5. View analytics globally or for one job:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
snoopy analytics
|
|
140
|
+
snoopy analytics <jobRef>
|
|
141
|
+
snoopy analytics --days 7
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
6. Regenerate results CSV files (all jobs or one job):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
snoopy export csv
|
|
148
|
+
snoopy export csv <jobRef>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
7. Inspect one run's detailed log output:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
snoopy logs
|
|
155
|
+
snoopy logs <runId>
|
|
156
|
+
snoopy logs <runId> --raw
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
When `runId` is omitted for `logs`, Snoopy first prompts for a job, then prompts for a run from that job (up/down arrows + Enter).
|
|
160
|
+
|
|
161
|
+
8. Show recent errors for one job:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
snoopy errors <jobRef>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
9. Enable daemon mode:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
snoopy daemon start
|
|
171
|
+
snoopy daemon reload
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Most Used Commands
|
|
175
|
+
|
|
176
|
+
- `job add`
|
|
177
|
+
- `job list`
|
|
178
|
+
- `job run [jobRef] --limit <N>`
|
|
179
|
+
- `job runs [jobRef]`
|
|
180
|
+
- `analytics [jobRef] --days <N>`
|
|
181
|
+
- `export csv [jobRef]`
|
|
182
|
+
- `logs [runId]`
|
|
183
|
+
- `errors [jobRef] --hours <N>`
|
|
184
|
+
- `start [jobRef]` / `stop [jobRef]`
|
|
185
|
+
- `delete [jobRef]`
|
|
186
|
+
- `daemon start|stop|status`
|
|
187
|
+
- `daemon reload`
|
|
188
|
+
- `startup status`
|
|
189
|
+
- `doctor`
|
|
190
|
+
|
|
191
|
+
## Run Logs
|
|
192
|
+
|
|
193
|
+
- Each job run writes a dedicated log file under `~/.snoopy/logs/`.
|
|
194
|
+
- Files are named `run-<runId>.log`.
|
|
195
|
+
- `snoopy logs` now supports guided selection (job first, then run) and shows a pretty timeline by default with post/comment text snippets, qualification result + justification, and clickable post/comment links.
|
|
196
|
+
- Use `snoopy logs [runId] --raw` to print the full raw log file content, including full JSON request/response payloads for Reddit and OpenRouter calls.
|
|
197
|
+
- Rich TTY manual runs (`snoopy job run <jobRef>`) show compact multi-line scan blocks with indented fields, clickable links, and qualification justifications.
|
|
198
|
+
- In rich terminals, scan field labels are colorized and qualification status is highlighted (`qualified` in green, `not qualified` in red, `pending` in yellow).
|
|
199
|
+
- Run logs older than 5 days are deleted automatically on daemon startup and after each job run.
|
|
200
|
+
- Deleting a job also deletes all associated per-run log files for that job.
|
|
201
|
+
|
|
202
|
+
## Results CSV Exports
|
|
203
|
+
|
|
204
|
+
- Export files are generated on demand with `export csv`.
|
|
205
|
+
- Files are written under `~/.snoopy/results/`.
|
|
206
|
+
- Each job gets one file named `<job-slug>.csv`.
|
|
207
|
+
- CSV files are regenerated from database truth on each export command.
|
|
208
|
+
- Deleting a job also deletes that job's CSV file.
|
|
209
|
+
|
|
210
|
+
## Live E2E Smoke Test
|
|
211
|
+
|
|
212
|
+
Use the built-in smoke harness to verify create -> run(5) -> delete:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npm run e2e:smoke
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Optional env vars:
|
|
219
|
+
- `SNOOPY_E2E_LIMIT` (default `5`)
|
|
220
|
+
- `SNOOPY_E2E_SUBREDDITS` (default `startups,entrepreneur`)
|
|
221
|
+
- `SNOOPY_E2E_KEEP_JOB=true` to skip cleanup for debugging
|
|
222
|
+
|
|
223
|
+
## Full Documentation
|
|
224
|
+
|
|
225
|
+
- [Documentation Index](docs/README.md)
|
|
226
|
+
- [Installation & Setup](docs/installation-and-setup.md)
|
|
227
|
+
- [Command Reference](docs/commands/index.md)
|
|
228
|
+
- [Database Schema](docs/database-schema.md)
|
|
229
|
+
- [Agent DB Operations](docs/agents-db.md)
|
|
230
|
+
- [Scheduling, Cron, Daemon, and Startup](docs/scheduling-and-startup.md)
|
|
231
|
+
- [Security and Secret Storage](docs/security.md)
|
|
232
|
+
- [E2E Smoke Testing Guide](docs/e2e-testing.md)
|
|
233
|
+
|
|
234
|
+
## Docs Site
|
|
235
|
+
|
|
236
|
+
Serve the Docusaurus docs site locally:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
npm run docs:start
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Build and preview the static docs site:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
npm run docs:build
|
|
246
|
+
npm run docs:serve
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Deploy to GitHub Pages:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
GITHUB_OWNER=telepat-io GITHUB_REPO=snoopy npm run docs:deploy
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Docs changes pushed to `main` under `docs/` or `website/` are also rebuilt and published to GitHub Pages automatically via GitHub Actions.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { JobsRepository } from '../../services/db/repositories/jobsRepo.js';
|
|
2
|
+
import { AnalyticsService } from '../../services/analytics/analyticsService.js';
|
|
3
|
+
import { printCommandScreen, printError, printInfo, printKeyValue, printMuted, printSection, printWarning } from '../ui/consoleUi.js';
|
|
4
|
+
import { formatRunDisplayTimestamp } from '../ui/time.js';
|
|
5
|
+
function formatInteger(value) {
|
|
6
|
+
return new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value);
|
|
7
|
+
}
|
|
8
|
+
function formatFloat(value, digits = 2) {
|
|
9
|
+
return new Intl.NumberFormat('en-US', {
|
|
10
|
+
minimumFractionDigits: digits,
|
|
11
|
+
maximumFractionDigits: digits
|
|
12
|
+
}).format(value);
|
|
13
|
+
}
|
|
14
|
+
function formatUsd(value) {
|
|
15
|
+
return `$${value.toFixed(6)} (est.)`;
|
|
16
|
+
}
|
|
17
|
+
function formatMaybe(value, digits = 2) {
|
|
18
|
+
if (value === null) {
|
|
19
|
+
return '-';
|
|
20
|
+
}
|
|
21
|
+
return formatFloat(value, digits);
|
|
22
|
+
}
|
|
23
|
+
function printMetricsBlock(metrics) {
|
|
24
|
+
printKeyValue('New posts scanned', formatInteger(metrics.newPosts));
|
|
25
|
+
printKeyValue('New comments scanned', formatInteger(metrics.newComments));
|
|
26
|
+
printKeyValue('Prompt tokens', formatInteger(metrics.promptTokens));
|
|
27
|
+
printKeyValue('Completion tokens', formatInteger(metrics.completionTokens));
|
|
28
|
+
printKeyValue('Total tokens', formatInteger(metrics.totalTokens));
|
|
29
|
+
printKeyValue('Estimated cost', formatUsd(metrics.estimatedCostUsd));
|
|
30
|
+
printKeyValue('Avg posts/day', formatFloat(metrics.avgNewPostsPerDay));
|
|
31
|
+
printKeyValue('Avg comments/day', formatFloat(metrics.avgNewCommentsPerDay));
|
|
32
|
+
printKeyValue('Avg tokens/day', formatFloat(metrics.avgTotalTokensPerDay));
|
|
33
|
+
printKeyValue('Avg cost/day', formatUsd(metrics.avgEstimatedCostUsdPerDay));
|
|
34
|
+
printKeyValue('Tokens per post', formatMaybe(metrics.tokensPerPost));
|
|
35
|
+
printKeyValue('Cost per post', metrics.costPerPost === null ? '-' : formatUsd(metrics.costPerPost));
|
|
36
|
+
}
|
|
37
|
+
function printRunCard(run) {
|
|
38
|
+
printInfo(`${formatRunDisplayTimestamp(run)} ${run.jobName ?? run.jobId}`);
|
|
39
|
+
printKeyValue('Run ID', run.id);
|
|
40
|
+
printKeyValue('Status', run.status);
|
|
41
|
+
printKeyValue('Duration', run.durationSeconds === null ? '-' : `${run.durationSeconds}s`);
|
|
42
|
+
printKeyValue('New posts', formatInteger(run.newPosts));
|
|
43
|
+
printKeyValue('New comments', formatInteger(run.newComments));
|
|
44
|
+
printKeyValue('Prompt tokens', formatInteger(run.promptTokens));
|
|
45
|
+
printKeyValue('Completion tokens', formatInteger(run.completionTokens));
|
|
46
|
+
printKeyValue('Total tokens', formatInteger(run.totalTokens));
|
|
47
|
+
printKeyValue('Estimated cost', run.estimatedCostUsd === null ? '-' : formatUsd(run.estimatedCostUsd));
|
|
48
|
+
printKeyValue('Tokens per post', formatMaybe(run.tokensPerPost));
|
|
49
|
+
printKeyValue('Cost per post', run.costPerPost === null ? '-' : formatUsd(run.costPerPost));
|
|
50
|
+
printKeyValue('Log', run.logFilePath ?? '-');
|
|
51
|
+
if (run.message) {
|
|
52
|
+
printWarning(`Message: ${run.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function showAnalytics(jobRef, options = {}) {
|
|
56
|
+
const days = options.days ?? 30;
|
|
57
|
+
const jobsRepo = new JobsRepository();
|
|
58
|
+
const analyticsService = new AnalyticsService();
|
|
59
|
+
printCommandScreen('Analytics');
|
|
60
|
+
if (jobRef) {
|
|
61
|
+
const job = jobsRepo.getByRef(jobRef);
|
|
62
|
+
if (!job) {
|
|
63
|
+
printError(`Job not found: ${jobRef}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const view = analyticsService.getJobAnalytics(job.id, { days });
|
|
67
|
+
printSection(`Job Analytics: ${job.name} (${job.slug})`);
|
|
68
|
+
printKeyValue('Window', `Last ${view.windowDays} day(s)`);
|
|
69
|
+
printKeyValue('Runs in window', formatInteger(view.runCount));
|
|
70
|
+
printMetricsBlock(view.totals);
|
|
71
|
+
printSection('Subreddit Breakdown');
|
|
72
|
+
if (view.bySubreddit.length === 0) {
|
|
73
|
+
printWarning('No subreddit analytics yet for this job in the selected window.');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
view.bySubreddit.forEach((row) => {
|
|
77
|
+
printInfo(`r/${row.subreddit}`);
|
|
78
|
+
printKeyValue('New posts/comments', `${formatInteger(row.newPosts)}/${formatInteger(row.newComments)}`);
|
|
79
|
+
printKeyValue('Total tokens', formatInteger(row.metrics.totalTokens));
|
|
80
|
+
printKeyValue('Estimated cost', formatUsd(row.metrics.estimatedCostUsd));
|
|
81
|
+
printKeyValue('Avg posts/comments per day', `${formatFloat(row.metrics.avgNewPostsPerDay)}/${formatFloat(row.metrics.avgNewCommentsPerDay)}`);
|
|
82
|
+
printKeyValue('Avg tokens/day', formatFloat(row.metrics.avgTotalTokensPerDay));
|
|
83
|
+
printKeyValue('Avg cost/day', formatUsd(row.metrics.avgEstimatedCostUsdPerDay));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
printSection('Recent Runs');
|
|
87
|
+
if (view.recentRuns.length === 0) {
|
|
88
|
+
printWarning('No runs found in the selected window.');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
view.recentRuns.forEach((run, index) => {
|
|
92
|
+
printRunCard(run);
|
|
93
|
+
if (index < view.recentRuns.length - 1) {
|
|
94
|
+
printMuted('');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const view = analyticsService.getGlobalAnalytics({ days });
|
|
100
|
+
printSection('System Analytics');
|
|
101
|
+
printKeyValue('Window', `Last ${view.windowDays} day(s)`);
|
|
102
|
+
printKeyValue('Runs in window', formatInteger(view.runCount));
|
|
103
|
+
printMetricsBlock(view.totals);
|
|
104
|
+
printSection('Job Breakdown');
|
|
105
|
+
if (view.byJob.length === 0) {
|
|
106
|
+
printWarning('No job analytics yet in the selected window.');
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
view.byJob.forEach((row) => {
|
|
110
|
+
printInfo(`${row.jobName} (${row.jobSlug})`);
|
|
111
|
+
printKeyValue('Job ID', row.jobId);
|
|
112
|
+
printKeyValue('New posts/comments', `${formatInteger(row.newPosts)}/${formatInteger(row.newComments)}`);
|
|
113
|
+
printKeyValue('Total tokens', formatInteger(row.metrics.totalTokens));
|
|
114
|
+
printKeyValue('Estimated cost', formatUsd(row.metrics.estimatedCostUsd));
|
|
115
|
+
printKeyValue('Avg posts/comments per day', `${formatFloat(row.metrics.avgNewPostsPerDay)}/${formatFloat(row.metrics.avgNewCommentsPerDay)}`);
|
|
116
|
+
printKeyValue('Avg tokens/day', formatFloat(row.metrics.avgTotalTokensPerDay));
|
|
117
|
+
printKeyValue('Avg cost/day', formatUsd(row.metrics.avgEstimatedCostUsdPerDay));
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
printSection('Subreddit Breakdown');
|
|
121
|
+
if (view.bySubreddit.length === 0) {
|
|
122
|
+
printWarning('No subreddit analytics yet in the selected window.');
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
view.bySubreddit.forEach((row) => {
|
|
126
|
+
printInfo(`r/${row.subreddit}`);
|
|
127
|
+
printKeyValue('New posts/comments', `${formatInteger(row.newPosts)}/${formatInteger(row.newComments)}`);
|
|
128
|
+
printKeyValue('Total tokens', formatInteger(row.metrics.totalTokens));
|
|
129
|
+
printKeyValue('Estimated cost', formatUsd(row.metrics.estimatedCostUsd));
|
|
130
|
+
printKeyValue('Avg posts/comments per day', `${formatFloat(row.metrics.avgNewPostsPerDay)}/${formatFloat(row.metrics.avgNewCommentsPerDay)}`);
|
|
131
|
+
printKeyValue('Avg tokens/day', formatFloat(row.metrics.avgTotalTokensPerDay));
|
|
132
|
+
printKeyValue('Avg cost/day', formatUsd(row.metrics.avgEstimatedCostUsdPerDay));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
printSection('Recent Runs');
|
|
136
|
+
if (view.recentRuns.length === 0) {
|
|
137
|
+
printWarning('No runs found in the selected window.');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
view.recentRuns.forEach((run, index) => {
|
|
141
|
+
printRunCard(run);
|
|
142
|
+
if (index < view.recentRuns.length - 1) {
|
|
143
|
+
printMuted('');
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../../src/cli/commands/analytics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { CronScheduler } from '../../services/scheduler/cronScheduler.js';
|
|
3
|
+
import { cleanupOldLogs } from '../../services/logging/logRotation.js';
|
|
4
|
+
import { ensureAppDirs } from '../../utils/paths.js';
|
|
5
|
+
import { ensureDaemonRunning, isDaemonRunning, requestDaemonReload } from '../../services/daemonControl.js';
|
|
6
|
+
import { printCommandScreen, printError, printInfo, printMuted, printSuccess, printWarning } from '../ui/consoleUi.js';
|
|
7
|
+
let scheduler = null;
|
|
8
|
+
export function daemonRun() {
|
|
9
|
+
ensureAppDirs();
|
|
10
|
+
cleanupOldLogs();
|
|
11
|
+
printCommandScreen('Daemon mode', 'Daemon');
|
|
12
|
+
scheduler = new CronScheduler();
|
|
13
|
+
scheduler.start();
|
|
14
|
+
process.on('SIGTERM', () => {
|
|
15
|
+
scheduler?.stop();
|
|
16
|
+
process.exit(0);
|
|
17
|
+
});
|
|
18
|
+
process.on('SIGINT', () => {
|
|
19
|
+
scheduler?.stop();
|
|
20
|
+
process.exit(0);
|
|
21
|
+
});
|
|
22
|
+
process.on('SIGUSR2', () => {
|
|
23
|
+
scheduler?.reload();
|
|
24
|
+
});
|
|
25
|
+
setInterval(() => {
|
|
26
|
+
// Keep event loop alive for cron tasks.
|
|
27
|
+
}, 60_000);
|
|
28
|
+
printSuccess('Snoopy daemon is running.');
|
|
29
|
+
printMuted('Press Ctrl+C to stop.');
|
|
30
|
+
}
|
|
31
|
+
export function daemonReload() {
|
|
32
|
+
printCommandScreen('Daemon control', 'Daemon Reload');
|
|
33
|
+
const result = requestDaemonReload();
|
|
34
|
+
if (!result.pid) {
|
|
35
|
+
printWarning('Daemon is not running.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (!result.reloaded) {
|
|
39
|
+
printError(`Could not reload daemon schedules for pid ${result.pid}.`);
|
|
40
|
+
printMuted('If this persists, restart the daemon with snoopy daemon stop && snoopy daemon start.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
printSuccess(`Reloaded daemon schedules (pid ${result.pid}).`);
|
|
44
|
+
}
|
|
45
|
+
export function daemonStart() {
|
|
46
|
+
printCommandScreen('Daemon control', 'Daemon Start');
|
|
47
|
+
const status = ensureDaemonRunning();
|
|
48
|
+
if (!status.started) {
|
|
49
|
+
printWarning(`Daemon already running${status.pid ? ` (pid ${status.pid})` : ''}.`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
printSuccess(`Daemon started (pid ${status.pid}).`);
|
|
53
|
+
}
|
|
54
|
+
export function daemonStop() {
|
|
55
|
+
printCommandScreen('Daemon control', 'Daemon Stop');
|
|
56
|
+
const paths = ensureAppDirs();
|
|
57
|
+
if (!fs.existsSync(paths.pidFilePath)) {
|
|
58
|
+
printWarning('Daemon is not running.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const pid = Number(fs.readFileSync(paths.pidFilePath, 'utf8'));
|
|
62
|
+
try {
|
|
63
|
+
process.kill(pid, 'SIGTERM');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Ignore if already dead.
|
|
67
|
+
}
|
|
68
|
+
fs.unlinkSync(paths.pidFilePath);
|
|
69
|
+
printSuccess('Daemon stopped.');
|
|
70
|
+
}
|
|
71
|
+
export function daemonStatus() {
|
|
72
|
+
printCommandScreen('Daemon control', 'Daemon Status');
|
|
73
|
+
const status = isDaemonRunning();
|
|
74
|
+
if (!status.pid) {
|
|
75
|
+
printInfo('Daemon status: stopped');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (status.running) {
|
|
79
|
+
printSuccess(`Daemon status: running (pid ${status.pid})`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
printError(`Daemon status: stale pid file (pid ${status.pid})`);
|
|
83
|
+
printMuted('Run snoopy daemon stop to clear stale state.');
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../../../src/cli/commands/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDoctor(): Promise<void>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { getDb } from '../../services/db/sqlite.js';
|
|
3
|
+
import { JobsRepository } from '../../services/db/repositories/jobsRepo.js';
|
|
4
|
+
import { RunsRepository } from '../../services/db/repositories/runsRepo.js';
|
|
5
|
+
import { extractErrorEntries, readRunLog } from '../../services/logging/logReader.js';
|
|
6
|
+
import { getOpenRouterApiKey } from '../../services/security/secretStore.js';
|
|
7
|
+
import { getStartupStatus } from '../../services/startup/index.js';
|
|
8
|
+
import { ensureAppDirs } from '../../utils/paths.js';
|
|
9
|
+
import { printCommandScreen, printError, printInfo, printKeyValue, printSection, printSuccess, printWarning } from '../ui/consoleUi.js';
|
|
10
|
+
import { formatRunDisplayTimestamp } from '../ui/time.js';
|
|
11
|
+
function getDaemonHealth() {
|
|
12
|
+
const paths = ensureAppDirs();
|
|
13
|
+
if (!fs.existsSync(paths.pidFilePath)) {
|
|
14
|
+
return { ok: false, details: 'Daemon not running (no pid file)' };
|
|
15
|
+
}
|
|
16
|
+
const pid = Number(fs.readFileSync(paths.pidFilePath, 'utf8'));
|
|
17
|
+
if (!Number.isFinite(pid)) {
|
|
18
|
+
return { ok: false, details: 'Invalid daemon pid file' };
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
process.kill(pid, 0);
|
|
22
|
+
return { ok: true, details: `Daemon running (pid ${pid})` };
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return { ok: false, details: `Stale daemon pid file (pid ${pid})` };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isWithinLast24Hours(createdAt) {
|
|
29
|
+
const timestamp = Date.parse(createdAt);
|
|
30
|
+
if (Number.isNaN(timestamp)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return timestamp >= Date.now() - 24 * 60 * 60 * 1000;
|
|
34
|
+
}
|
|
35
|
+
export async function runDoctor() {
|
|
36
|
+
printCommandScreen('Diagnostics', 'Snoopy Doctor');
|
|
37
|
+
const paths = ensureAppDirs();
|
|
38
|
+
let dbOk = false;
|
|
39
|
+
let dbDetails = `DB file: ${paths.dbPath}`;
|
|
40
|
+
try {
|
|
41
|
+
const db = getDb();
|
|
42
|
+
db.prepare('SELECT 1').get();
|
|
43
|
+
dbOk = true;
|
|
44
|
+
dbDetails = `DB reachable at ${paths.dbPath}`;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
dbDetails = `DB error: ${String(error)}`;
|
|
48
|
+
}
|
|
49
|
+
const jobsRepo = new JobsRepository();
|
|
50
|
+
const runsRepo = new RunsRepository();
|
|
51
|
+
const jobs = jobsRepo.list();
|
|
52
|
+
const enabledJobs = jobs.filter((job) => job.enabled).length;
|
|
53
|
+
const apiKey = await getOpenRouterApiKey();
|
|
54
|
+
const startup = getStartupStatus();
|
|
55
|
+
const daemon = getDaemonHealth();
|
|
56
|
+
printKeyValue('Platform', process.platform);
|
|
57
|
+
printKeyValue('Node', process.version);
|
|
58
|
+
if (dbOk) {
|
|
59
|
+
printSuccess(`Database: ${dbDetails}`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
printError(`Database: ${dbDetails}`);
|
|
63
|
+
}
|
|
64
|
+
if (apiKey) {
|
|
65
|
+
printSuccess('OpenRouter API key: configured');
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
printWarning('OpenRouter API key: missing');
|
|
69
|
+
}
|
|
70
|
+
printInfo(`Jobs: ${jobs.length} total, ${enabledJobs} enabled`);
|
|
71
|
+
if (daemon.ok) {
|
|
72
|
+
printSuccess(`Daemon: ${daemon.details}`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
printWarning(`Daemon: ${daemon.details}`);
|
|
76
|
+
}
|
|
77
|
+
printInfo(`Startup on reboot: ${startup.enabled ? 'enabled' : 'disabled'} via ${startup.method}`);
|
|
78
|
+
printInfo(`Startup details: ${startup.detail}`);
|
|
79
|
+
printSection('Recent Job Errors');
|
|
80
|
+
const recentProblemRuns = runsRepo
|
|
81
|
+
.latestWithJobNames(20)
|
|
82
|
+
.filter((run) => isWithinLast24Hours(run.createdAt))
|
|
83
|
+
.map((run) => {
|
|
84
|
+
const logContent = readRunLog(run.logFilePath);
|
|
85
|
+
const errorEntries = extractErrorEntries(logContent ?? '');
|
|
86
|
+
return {
|
|
87
|
+
run,
|
|
88
|
+
errorEntries
|
|
89
|
+
};
|
|
90
|
+
})
|
|
91
|
+
.filter(({ run, errorEntries }) => run.status === 'failed' || errorEntries.length > 0);
|
|
92
|
+
if (recentProblemRuns.length === 0) {
|
|
93
|
+
printSuccess('No recent job run failures or logged errors in the last 24 hours.');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
printWarning(`Found ${recentProblemRuns.length} recent run(s) with failures or logged errors.`);
|
|
97
|
+
recentProblemRuns.forEach(({ run, errorEntries }) => {
|
|
98
|
+
printWarning(`${formatRunDisplayTimestamp(run)} ${run.jobName ?? run.jobId} (${run.status})`);
|
|
99
|
+
printKeyValue('Run ID', run.id);
|
|
100
|
+
printKeyValue('Message', run.message ?? '-');
|
|
101
|
+
if (errorEntries.length > 0) {
|
|
102
|
+
printInfo(errorEntries[errorEntries.length - 1].split('\n')[0]);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"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"}
|