@codigoconelmer/driftwatch 1.1.5 → 1.2.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DriftWatch
2
2
 
3
- External API schema drift detector with Telegram alerts. No SDK to install in your apps runs as a standalone daemon or Docker container, pointing at any HTTP endpoint you want to monitor.
3
+ External API schema drift detector. Monitors HTTP endpoints and alerts you when the response shape changeskeys added, removed, or type changed. Runs as a standalone daemon or Docker container, no SDK to install in your apps.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@codigoconelmer/driftwatch)](https://www.npmjs.com/package/@codigoconelmer/driftwatch)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
@@ -10,7 +10,7 @@ External API schema drift detector with Telegram alerts. No SDK to install in yo
10
10
 
11
11
  ## What it does
12
12
 
13
- DriftWatch periodically hits your API endpoints and extracts their response **schema** — keys and types, not values. When the schema changes (keys added, removed, or type changed), it fires a Telegram alert instantly.
13
+ DriftWatch periodically hits your API endpoints and extracts their response **schema** — keys and types, not values. When the schema changes, it fires an alert via Telegram, Slack, or Discord instantly.
14
14
 
15
15
  ```
16
16
  ⚠️ Schema drift detected!
@@ -37,42 +37,14 @@ pnpm add -g @codigoconelmer/driftwatch
37
37
 
38
38
  ## Quick start
39
39
 
40
- **1. Run the interactive setup**
40
+ **1. Interactive setup**
41
41
 
42
42
  ```bash
43
43
  cd my-project/
44
44
  driftwatch init
45
45
  ```
46
46
 
47
- The CLI will ask you everything — endpoint URLs, auth tokens, check interval, and optionally Telegram. No manual YAML editing required.
48
-
49
- ```
50
- ── Endpoint setup ──────────────────────────
51
-
52
- ? Endpoint name: Cards
53
- ? URL: https://api.myapp.com/api/cards
54
- ? HTTP method: GET
55
- ? Requires auth header (Bearer token)? Yes
56
- ? Bearer token value (will be saved to .env as API_TOKEN): ********
57
- ? Check interval: Every 5 minutes
58
- ? Add another endpoint? No
59
-
60
- ── Telegram alerts (optional) ──────────────
61
-
62
- ? Set up Telegram alerts now? Yes
63
- ? Bot token: ********
64
- ? Chat ID: 12345678
65
-
66
- ✓ Created driftwatch.config.yml
67
- ✓ Updated .env
68
- ✓ Updated .env.example
69
-
70
- Run "driftwatch check" to create your first snapshot.
71
- ```
72
-
73
- > Telegram is optional — skip it during init and add it later by setting `TELEGRAM_TOKEN` and `CHAT_ID` in `.env`. Without it, drift is logged to console only.
74
-
75
- **2. First run** — creates snapshots, no alerts sent
47
+ **2. Create baseline snapshots** (first run, no alerts sent)
76
48
 
77
49
  ```bash
78
50
  driftwatch check
@@ -81,7 +53,8 @@ driftwatch check
81
53
  **3. Start the daemon**
82
54
 
83
55
  ```bash
84
- driftwatch start
56
+ driftwatch start # foreground
57
+ driftwatch start --daemon # background
85
58
  ```
86
59
 
87
60
  ---
@@ -89,9 +62,18 @@ driftwatch start
89
62
  ## Config reference
90
63
 
91
64
  ```yaml
92
- telegram:
93
- bot_token: '${TELEGRAM_TOKEN}'
94
- chat_id: '${CHAT_ID}'
65
+ # Alert channels (all optional — pick one or more)
66
+ alerts:
67
+ telegram:
68
+ bot_token: '${TELEGRAM_TOKEN}'
69
+ chat_id: '${CHAT_ID}'
70
+ slack:
71
+ webhook_url: '${SLACK_WEBHOOK}'
72
+ discord:
73
+ webhook_url: '${DISCORD_WEBHOOK}'
74
+
75
+ # Global cooldown: minimum minutes between repeated alerts per endpoint
76
+ alert_cooldown: 30
95
77
 
96
78
  endpoints:
97
79
  - name: 'Cards'
@@ -101,6 +83,11 @@ endpoints:
101
83
  Authorization: 'Bearer ${API_TOKEN}'
102
84
  Accept: 'application/json'
103
85
  interval: '*/5 * * * *'
86
+ retries: 3 # retry on 5xx/timeout before alerting (default: 0)
87
+ retry_delay: 10 # seconds between retries (default: 5)
88
+ ignore_fields: # field names to exclude from drift detection
89
+ - updated_at
90
+ - expires_at
104
91
 
105
92
  - name: 'Create order'
106
93
  url: 'https://api.myapp.com/api/orders'
@@ -114,6 +101,12 @@ endpoints:
114
101
  interval: '0 * * * *'
115
102
  ```
116
103
 
104
+ `${VAR}` values are resolved from `.env` or environment variables.
105
+
106
+ > **Backwards compat:** The old top-level `telegram:` block still works — it's automatically normalized to `alerts.telegram` on load.
107
+
108
+ ### Endpoint fields
109
+
117
110
  | Field | Required | Description |
118
111
  |---|---|---|
119
112
  | `name` | yes | Display name used in alerts and snapshot filenames |
@@ -121,28 +114,46 @@ endpoints:
121
114
  | `method` | no | HTTP method, defaults to `GET` |
122
115
  | `headers` | no | Key/value headers sent with every request |
123
116
  | `body` | no | JSON body for POST/PUT/PATCH requests |
124
- | `interval` | yes | Cron expression (e.g. `*/5 * * * *` = every 5 min) |
117
+ | `interval` | yes | Cron expression (e.g. `*/5 * * * *`) |
118
+ | `retries` | no | Retry attempts on 5xx/timeout before alerting (default: 0) |
119
+ | `retry_delay` | no | Seconds between retries (default: 5) |
120
+ | `ignore_fields` | no | Field names to skip in drift detection |
125
121
 
126
- `${VAR}` values are resolved from `.env` or environment variables.
122
+ ### Global fields
123
+
124
+ | Field | Description |
125
+ |---|---|
126
+ | `alert_cooldown` | Minutes between repeated alerts per endpoint. Accepts `30`, `"30m"`, or `"2h"` |
127
+ | `alerts.telegram` | Telegram bot token + chat ID |
128
+ | `alerts.slack` | Slack incoming webhook URL |
129
+ | `alerts.discord` | Discord webhook URL |
127
130
 
128
131
  ---
129
132
 
130
133
  ## CLI
131
134
 
132
135
  ```bash
133
- driftwatch init # generate driftwatch.config.yml
134
- driftwatch start # start the daemon (cron-based)
135
- driftwatch start -c /path/to/cfg.yml # custom config path
136
- driftwatch check # one-shot check all endpoints now
137
- driftwatch check -c /path/to/cfg.yml # custom config path
136
+ driftwatch init # interactive setup wizard
137
+ driftwatch check # one-shot check all endpoints
138
+ driftwatch check -e "Cards" # check a single endpoint by name
139
+ driftwatch check -c /path/to/cfg.yml # use custom config path
140
+ driftwatch start # start cron daemon (foreground)
141
+ driftwatch start --daemon # start as background process
142
+ driftwatch stop # stop the background daemon
143
+ driftwatch status # show daemon state + last check per endpoint
144
+ driftwatch reset "Cards" # delete snapshot to force re-baseline
145
+ driftwatch ui # open web dashboard at localhost:4573
146
+ driftwatch ui --port 8080 # custom port
138
147
  ```
139
148
 
140
149
  ---
141
150
 
142
- ## Telegram setup (optional)
151
+ ## Alert channels
152
+
153
+ ### Telegram
143
154
 
144
- 1. Open Telegram → search `@BotFather` → send `/newbot` → copy the token
145
- 2. Send any message to your new bot
155
+ 1. Open Telegram → search `@BotFather` → `/newbot` → copy the token
156
+ 2. Send any message to your bot
146
157
  3. Open `https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates` in browser
147
158
  4. Copy the `chat.id` from the JSON response
148
159
  5. Add to `.env`:
@@ -152,7 +163,50 @@ TELEGRAM_TOKEN=your-bot-token
152
163
  CHAT_ID=your-chat-id
153
164
  ```
154
165
 
155
- Without Telegram configured, drift is logged to console only — no alerts sent.
166
+ ### Slack
167
+
168
+ 1. Go to [api.slack.com/apps](https://api.slack.com/apps) → Create App → Incoming Webhooks → Activate → Add Webhook
169
+ 2. Copy the webhook URL
170
+ 3. Add to `.env`: `SLACK_WEBHOOK=https://hooks.slack.com/services/...`
171
+
172
+ ### Discord
173
+
174
+ 1. Server Settings → Integrations → Webhooks → New Webhook → Copy URL
175
+ 2. Add to `.env`: `DISCORD_WEBHOOK=https://discord.com/api/webhooks/...`
176
+
177
+ Without any alert channel configured, drift is logged to console only.
178
+
179
+ ---
180
+
181
+ ## Daemon mode
182
+
183
+ ```bash
184
+ driftwatch start --daemon # forks to background, writes PID to .driftwatch/driftwatch.pid
185
+ # logs go to .driftwatch/driftwatch.log
186
+ driftwatch stop # kills daemon by PID
187
+ driftwatch status # shows running state + last result per endpoint
188
+ ```
189
+
190
+ Example `status` output:
191
+
192
+ ```
193
+ Daemon: running (PID 12345)
194
+
195
+ ✅ Cards — ok — 6/1/2026, 10:30:00 AM
196
+ ⚠️ Orders — drift — 6/1/2026, 09:15:00 AM
197
+ 🔴 Login — down (503 Service Unavailable) — 6/1/2026, 10:29:00 AM
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Web UI
203
+
204
+ ```bash
205
+ driftwatch ui
206
+ # → http://localhost:4573
207
+ ```
208
+
209
+ Local dashboard showing endpoint status and full drift history. Refreshes automatically every 30 seconds. No external dependencies.
156
210
 
157
211
  ---
158
212
 
@@ -160,53 +214,33 @@ Without Telegram configured, drift is logged to console only — no alerts sent.
160
214
 
161
215
  1. **First run per endpoint** — saves the response schema to `.driftwatch/snapshots/<name>.json`. No alert sent.
162
216
  2. **Subsequent runs** — compares live schema against the snapshot.
163
- - No change → logs "no drift", no alert.
164
- - Change detected → logs drift + sends Telegram alert (if configured), updates snapshot as new baseline.
165
- 3. **Schema** is a recursive key+type map. Values are ignored. Arrays are sampled from the first element — nested object structure is preserved.
166
- 4. **Auth** supports Bearer token and any custom headers. Cookie-based auth (e.g. Sanctum sessions) is not supported use stateless tokens only.
217
+ - No change → no alert.
218
+ - Change detected → alert sent (respecting cooldown), snapshot updated, event appended to `.driftwatch/history.json`.
219
+ - 5xx or timeout "endpoint down" alert sent after exhausting retries.
220
+ 3. **Schema** is a recursive key+type map. Values are ignored. Arrays are sampled from the first element.
221
+ 4. **Auth** — supports Bearer token and any custom headers. Cookie/session auth is not supported — use stateless tokens.
167
222
 
168
223
  ---
169
224
 
170
- ## Docker (self-host on VPS)
225
+ ## Docker
171
226
 
172
227
  ```bash
173
228
  cp .env.example .env
174
229
  # fill in your tokens
175
230
 
176
231
  docker compose up -d
177
- ```
178
-
179
- Snapshots persist in a named Docker volume (`driftwatch-snapshots`) so they survive container restarts.
180
-
181
- To view logs:
182
- ```bash
183
232
  docker compose logs -f
184
233
  ```
185
234
 
235
+ Snapshots persist in a named Docker volume (`driftwatch-snapshots`) and survive container restarts.
236
+
186
237
  ---
187
238
 
188
239
  ## Snapshots
189
240
 
190
- Snapshots are stored in `.driftwatch/snapshots/` as JSON files named after each endpoint. They are **gitignored by default** — each environment (local, staging, prod) should build its own baseline on first run.
191
-
192
- Example snapshot:
193
- ```json
194
- {
195
- "endpoint": "Cards",
196
- "url": "https://api.myapp.com/api/cards",
197
- "capturedAt": "2026-01-15T10:30:00.000Z",
198
- "schema": {
199
- "status": "boolean",
200
- "data": {
201
- "[]": {
202
- "id": "string",
203
- "name": "string",
204
- "email": "string"
205
- }
206
- }
207
- }
208
- }
209
- ```
241
+ Stored in `.driftwatch/snapshots/` as JSON files named after each endpoint (slugified). Gitignored by default — each environment builds its own baseline on first run.
242
+
243
+ Use `driftwatch reset "Endpoint Name"` to delete a snapshot and force re-baseline on the next check.
210
244
 
211
245
  ---
212
246
 
package/dist/checker.d.ts CHANGED
@@ -5,6 +5,8 @@ export interface CheckResult {
5
5
  endpoint: EndpointConfig;
6
6
  isFirstRun: boolean;
7
7
  hasDrift: boolean;
8
+ isDown: boolean;
9
+ downReason?: string;
8
10
  diff: DiffResult;
9
11
  }
10
12
  export declare function checkEndpoint(endpoint: EndpointConfig): Promise<CheckResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"checker.d.ts","sourceRoot":"","sources":["../src/checker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAgB,UAAU,EAA2B,MAAM,YAAY,CAAC;AAIjH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,WAAW,CAiBpE;AAkBD,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAuB5E;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAgClF"}
1
+ {"version":3,"file":"checker.d.ts","sourceRoot":"","sources":["../src/checker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAgB,UAAU,EAA2B,MAAM,YAAY,CAAC;AAIjH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,WAAW,CAiBpE;AAkBD,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CAuB5E;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,UAAU,CAAC;CAClB;AAoBD,wBAAsB,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAsDlF"}
package/dist/checker.js CHANGED
@@ -1,4 +1,4 @@
1
- import axios from 'axios';
1
+ import axios, { isAxiosError } from 'axios';
2
2
  import { readSnapshot, writeSnapshot } from './snapshot.js';
3
3
  const MAX_DEPTH = 20;
4
4
  export function extractSchema(value, depth = 0) {
@@ -58,32 +58,72 @@ export function diffSchemas(prev, curr) {
58
58
  }
59
59
  return { added, removed, changed };
60
60
  }
61
+ function applyIgnoreFields(diff, ignoreFields) {
62
+ if (!ignoreFields.length)
63
+ return diff;
64
+ const ignored = new Set(ignoreFields);
65
+ const isIgnored = (p) => {
66
+ const last = p.split('.').pop() ?? p;
67
+ return ignored.has(last) || ignored.has(p);
68
+ };
69
+ return {
70
+ added: diff.added.filter(e => !isIgnored(e.path)),
71
+ removed: diff.removed.filter(e => !isIgnored(e.path)),
72
+ changed: diff.changed.filter(e => !isIgnored(e.path)),
73
+ };
74
+ }
75
+ function sleep(ms) {
76
+ return new Promise(resolve => setTimeout(resolve, ms));
77
+ }
61
78
  export async function checkEndpoint(endpoint) {
62
- const response = await axios({
63
- method: endpoint.method,
64
- url: endpoint.url,
65
- headers: endpoint.headers ?? {},
66
- data: endpoint.body,
67
- timeout: 15000,
68
- maxContentLength: 5 * 1024 * 1024, // 5MB
69
- maxBodyLength: 1 * 1024 * 1024, // 1MB request body
70
- });
71
- const schema = extractSchema(response.data);
72
- const snapshot = readSnapshot(endpoint.name);
73
- if (!snapshot) {
74
- writeSnapshot(endpoint.name, endpoint.url, schema);
75
- return {
76
- endpoint,
77
- isFirstRun: true,
78
- hasDrift: false,
79
- diff: { added: [], removed: [], changed: [] },
80
- };
81
- }
82
- const diff = diffSchemas(snapshot.schema, schema);
83
- const hasDrift = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
84
- if (hasDrift) {
85
- writeSnapshot(endpoint.name, endpoint.url, schema);
79
+ const maxAttempts = (endpoint.retries ?? 0) + 1;
80
+ const retryDelayMs = (endpoint.retry_delay ?? 5) * 1000;
81
+ const emptyDiff = { added: [], removed: [], changed: [] };
82
+ let downReason;
83
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
84
+ try {
85
+ const response = await axios({
86
+ method: endpoint.method,
87
+ url: endpoint.url,
88
+ headers: endpoint.headers ?? {},
89
+ data: endpoint.body,
90
+ timeout: 15000,
91
+ maxContentLength: 5 * 1024 * 1024,
92
+ maxBodyLength: 1 * 1024 * 1024,
93
+ });
94
+ const schema = extractSchema(response.data);
95
+ const snapshot = readSnapshot(endpoint.name);
96
+ if (!snapshot) {
97
+ writeSnapshot(endpoint.name, endpoint.url, schema);
98
+ return { endpoint, isFirstRun: true, hasDrift: false, isDown: false, diff: emptyDiff };
99
+ }
100
+ const rawDiff = diffSchemas(snapshot.schema, schema);
101
+ const diff = applyIgnoreFields(rawDiff, endpoint.ignore_fields ?? []);
102
+ const hasDrift = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
103
+ if (hasDrift)
104
+ writeSnapshot(endpoint.name, endpoint.url, schema);
105
+ return { endpoint, isFirstRun: false, hasDrift, isDown: false, diff };
106
+ }
107
+ catch (err) {
108
+ if (isAxiosError(err)) {
109
+ if (err.response && err.response.status >= 500) {
110
+ downReason = `${err.response.status} ${err.response.statusText}`;
111
+ }
112
+ else if (!err.response) {
113
+ downReason = err.message;
114
+ }
115
+ else {
116
+ throw err; // 4xx — not "down", surface to caller
117
+ }
118
+ }
119
+ else {
120
+ throw err;
121
+ }
122
+ if (attempt < maxAttempts) {
123
+ await sleep(retryDelayMs);
124
+ }
125
+ }
86
126
  }
87
- return { endpoint, isFirstRun: false, hasDrift, diff };
127
+ return { endpoint, isFirstRun: false, hasDrift: false, isDown: true, downReason, diff: emptyDiff };
88
128
  }
89
129
  //# sourceMappingURL=checker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"checker.js","sourceRoot":"","sources":["../src/checker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"checker.js","sourceRoot":"","sources":["../src/checker.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAiBzC,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAsBtD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAiBzC,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAmCtD"}
package/dist/config.js CHANGED
@@ -34,6 +34,17 @@ export function loadConfig(configPath) {
34
34
  if (!ep.method)
35
35
  ep.method = 'GET';
36
36
  }
37
+ // Normalize top-level telegram → alerts.telegram for backwards compat
38
+ if (resolved.telegram && !resolved.alerts?.telegram) {
39
+ resolved.alerts ??= {};
40
+ resolved.alerts.telegram = resolved.telegram;
41
+ }
42
+ // Parse alert_cooldown: support "30m", "2h", or plain number (minutes)
43
+ const cooldownRaw = resolved['alert_cooldown'];
44
+ if (typeof cooldownRaw === 'string') {
45
+ const match = cooldownRaw.match(/^(\d+)(m|h)?$/);
46
+ resolved.alert_cooldown = match ? parseInt(match[1], 10) * (match[2] === 'h' ? 60 : 1) : undefined;
47
+ }
37
48
  return resolved;
38
49
  }
39
50
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC3D,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CACzF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,UAAmB;IAC5C,MAAM,QAAQ,GAAG,UAAU,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,uBAAuB,CAAC,CAAC;IAEpF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,wCAAwC,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAW,CAAC;IAE/C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1E,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,MAAM,EAAE,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACpC,IAAI,CAAC,EAAE,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACzD,IAAI,CAAC,EAAE,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC,IAAI,iBAAiB,CAAC,CAAC;QACpE,IAAI,CAAC,EAAE,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC,IAAI,sBAAsB,CAAC,CAAC;QAC9E,IAAI,CAAC,EAAE,CAAC,MAAM;YAAE,EAAE,CAAC,MAAM,GAAG,KAAK,CAAC;IACpC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function startDaemon(configPath?: string): void;
2
+ export declare function stopDaemon(): void;
3
+ export declare function isDaemonRunning(): {
4
+ running: boolean;
5
+ pid?: number;
6
+ };
7
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAQA,wBAAgB,WAAW,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAiBrD;AAED,wBAAgB,UAAU,IAAI,IAAI,CAcjC;AAED,wBAAgB,eAAe,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CASpE"}
package/dist/daemon.js ADDED
@@ -0,0 +1,50 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { spawn } from 'child_process';
4
+ const DRIFTWATCH_DIR = path.resolve(process.cwd(), '.driftwatch');
5
+ const PID_FILE = path.join(DRIFTWATCH_DIR, 'driftwatch.pid');
6
+ const LOG_FILE = path.join(DRIFTWATCH_DIR, 'driftwatch.log');
7
+ export function startDaemon(configPath) {
8
+ const args = ['start'];
9
+ if (configPath)
10
+ args.push('-c', configPath);
11
+ fs.mkdirSync(DRIFTWATCH_DIR, { recursive: true });
12
+ const out = fs.openSync(LOG_FILE, 'a');
13
+ const child = spawn(process.execPath, [process.argv[1], ...args], {
14
+ detached: true,
15
+ stdio: ['ignore', out, out],
16
+ });
17
+ fs.writeFileSync(PID_FILE, String(child.pid));
18
+ child.unref();
19
+ console.log(`[driftwatch] Daemon started (PID ${child.pid})`);
20
+ console.log(`[driftwatch] Logs: ${LOG_FILE}`);
21
+ }
22
+ export function stopDaemon() {
23
+ if (!fs.existsSync(PID_FILE)) {
24
+ console.log('[driftwatch] No daemon running (no PID file found).');
25
+ return;
26
+ }
27
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
28
+ try {
29
+ process.kill(pid, 'SIGTERM');
30
+ fs.unlinkSync(PID_FILE);
31
+ console.log(`[driftwatch] Daemon stopped (PID ${pid}).`);
32
+ }
33
+ catch {
34
+ console.log(`[driftwatch] Process ${pid} not found — cleaning up PID file.`);
35
+ fs.unlinkSync(PID_FILE);
36
+ }
37
+ }
38
+ export function isDaemonRunning() {
39
+ if (!fs.existsSync(PID_FILE))
40
+ return { running: false };
41
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
42
+ try {
43
+ process.kill(pid, 0);
44
+ return { running: true, pid };
45
+ }
46
+ catch {
47
+ return { running: false, pid };
48
+ }
49
+ }
50
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,CAAC,CAAC;AAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;AAC7D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;AAE7D,MAAM,UAAU,WAAW,CAAC,UAAmB;IAC7C,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACvB,IAAI,UAAU;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAE5C,EAAE,CAAC,SAAS,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAEvC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE;QAChE,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,CAAC;KAC5B,CAAC,CAAC;IAEH,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9C,KAAK,CAAC,KAAK,EAAE,CAAC;IAEd,OAAO,CAAC,GAAG,CAAC,oCAAoC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC,sBAAsB,QAAQ,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;QACnE,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACnE,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC7B,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,oCAAoC,GAAG,IAAI,CAAC,CAAC;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,wBAAwB,GAAG,oCAAoC,CAAC,CAAC;QAC7E,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACxD,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACnE,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACjC,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { DiffResult } from './types.js';
2
+ export interface HistoryEntry {
3
+ timestamp: string;
4
+ endpoint: string;
5
+ url: string;
6
+ diff: DiffResult;
7
+ }
8
+ export declare function appendHistory(endpoint: string, url: string, diff: DiffResult): void;
9
+ export declare function readHistory(): HistoryEntry[];
10
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAUnF;AAED,wBAAgB,WAAW,IAAI,YAAY,EAAE,CAG5C"}
@@ -0,0 +1,28 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ const HISTORY_FILE = path.resolve(process.cwd(), '.driftwatch', 'history.json');
4
+ export function appendHistory(endpoint, url, diff) {
5
+ let entries = [];
6
+ if (fs.existsSync(HISTORY_FILE)) {
7
+ try {
8
+ entries = JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf8'));
9
+ }
10
+ catch { }
11
+ }
12
+ entries.push({ timestamp: new Date().toISOString(), endpoint, url, diff });
13
+ fs.mkdirSync(path.dirname(HISTORY_FILE), { recursive: true });
14
+ const tmp = `${HISTORY_FILE}.tmp`;
15
+ fs.writeFileSync(tmp, JSON.stringify(entries, null, 2));
16
+ fs.renameSync(tmp, HISTORY_FILE);
17
+ }
18
+ export function readHistory() {
19
+ if (!fs.existsSync(HISTORY_FILE))
20
+ return [];
21
+ try {
22
+ return JSON.parse(fs.readFileSync(HISTORY_FILE, 'utf8'));
23
+ }
24
+ catch {
25
+ return [];
26
+ }
27
+ }
28
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../src/history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,cAAc,CAAC,CAAC;AAShF,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,GAAW,EAAE,IAAgB;IAC3E,IAAI,OAAO,GAAmB,EAAE,CAAC;IACjC,IAAI,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC;YAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IAC/E,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3E,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAG,GAAG,YAAY,MAAM,CAAC;IAClC,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACxD,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5C,IAAI,CAAC;QAAC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC;IAAC,CAAC;AACxF,CAAC"}