@gowelle/stint-agent 1.2.35 → 1.2.37

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
@@ -141,18 +141,45 @@ stint status
141
141
 
142
142
  ### Desktop Notifications
143
143
 
144
- The daemon sends desktop notifications for important events (commit approved, new pending commits, project updates). Notifications are **enabled by default**.
144
+ The daemon sends desktop notifications for important events. Notifications can be controlled globally or per-category.
145
145
 
146
- **Disable notifications:**
146
+ #### Notification Categories
147
+
148
+ | Category | Events | Default |
149
+ | ------------- | --------------------------------------------- | ------- |
150
+ | `commits` | Commit approved, pending, pushed, failed | ✅ On |
151
+ | `sync` | Sync requested, project updated | ❌ Off |
152
+ | `suggestions` | New AI suggestions | ✅ On |
153
+
154
+ > **Note:** `sync` is disabled by default because these events fire frequently during active development.
155
+
156
+ #### Toggle All Notifications
147
157
 
148
158
  ```bash
159
+ # Disable all notifications
149
160
  stint config set notifications.enabled false
161
+
162
+ # Enable all notifications
163
+ stint config set notifications.enabled true
150
164
  ```
151
165
 
152
- **Enable notifications:**
166
+ #### Toggle by Category
153
167
 
154
168
  ```bash
155
- stint config set notifications.enabled true
169
+ # Disable sync notifications (noisy during development)
170
+ stint config set notifications.sync false
171
+
172
+ # Enable commit notifications
173
+ stint config set notifications.commits true
174
+
175
+ # Disable suggestion notifications
176
+ stint config set notifications.suggestions false
177
+ ```
178
+
179
+ #### View Current Settings
180
+
181
+ ```bash
182
+ stint config list
156
183
  ```
157
184
 
158
185
  > **Note:** Events are still logged even when notifications are disabled.
@@ -2,10 +2,10 @@ import {
2
2
  gitService,
3
3
  projectService,
4
4
  validatePidFile
5
- } from "./chunk-4655SBXG.js";
5
+ } from "./chunk-XRNTJYCQ.js";
6
6
  import {
7
7
  authService
8
- } from "./chunk-RC7Z6GTK.js";
8
+ } from "./chunk-HPHXBSGB.js";
9
9
 
10
10
  // src/components/StatusDashboard.tsx
11
11
  import { useState, useEffect } from "react";
@@ -14,7 +14,11 @@ import { Box as Box3, Text as Text3, useApp, useInput } from "ink";
14
14
  // src/components/Panel.tsx
15
15
  import { Box, Text } from "ink";
16
16
  import { jsx, jsxs } from "react/jsx-runtime";
17
- function Panel({ title, icon, children }) {
17
+ function Panel({
18
+ title,
19
+ icon,
20
+ children
21
+ }) {
18
22
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
19
23
  /* @__PURE__ */ jsxs(Text, { color: "blue", children: [
20
24
  icon ? `${icon} ` : "",
@@ -29,7 +33,11 @@ function Panel({ title, icon, children }) {
29
33
  // src/components/StatusRow.tsx
30
34
  import { Box as Box2, Text as Text2 } from "ink";
31
35
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
32
- function StatusRow({ label, value, labelWidth = 12 }) {
36
+ function StatusRow({
37
+ label,
38
+ value,
39
+ labelWidth = 12
40
+ }) {
33
41
  return /* @__PURE__ */ jsxs2(Box2, { children: [
34
42
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: label.padEnd(labelWidth) }),
35
43
  typeof value === "string" ? /* @__PURE__ */ jsx2(Text2, { children: value }) : value
@@ -38,7 +46,9 @@ function StatusRow({ label, value, labelWidth = 12 }) {
38
46
 
39
47
  // src/components/StatusDashboard.tsx
40
48
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
41
- function StatusDashboard({ cwd }) {
49
+ function StatusDashboard({
50
+ cwd
51
+ }) {
42
52
  const { exit } = useApp();
43
53
  const [state, setState] = useState({
44
54
  linkedProject: null,
@@ -101,12 +111,22 @@ function StatusDashboard({ cwd }) {
101
111
  setShowHelp((prev) => !prev);
102
112
  }
103
113
  });
104
- const { linkedProject, repoInfo, user, daemonRunning, daemonPid, isRepo, loading, lastRefresh, error } = state;
114
+ const {
115
+ linkedProject,
116
+ repoInfo,
117
+ user,
118
+ daemonRunning,
119
+ daemonPid,
120
+ isRepo,
121
+ loading,
122
+ lastRefresh,
123
+ error
124
+ } = state;
105
125
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", padding: 1, children: [
106
126
  /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E" }) }),
107
127
  /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
108
128
  /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "\u2502" }),
109
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: " \u{1F4CA} Stint Status Dashboard " }),
129
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: " \u{1F4CA} Stint Status Dashboard " }),
110
130
  /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "\u2502" })
111
131
  ] }),
112
132
  /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F" }) }),
@@ -116,16 +136,46 @@ function StatusDashboard({ cwd }) {
116
136
  ] }) }),
117
137
  loading && /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, children: /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "\u27F3 Refreshing..." }) }),
118
138
  /* @__PURE__ */ jsx3(Panel, { title: "Project Status", icon: "\u{1F4E6}", children: linkedProject ? /* @__PURE__ */ jsxs3(Fragment, { children: [
119
- /* @__PURE__ */ jsx3(StatusRow, { label: "Status:", value: /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 Linked" }) }),
139
+ /* @__PURE__ */ jsx3(
140
+ StatusRow,
141
+ {
142
+ label: "Status:",
143
+ value: /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 Linked" })
144
+ }
145
+ ),
120
146
  /* @__PURE__ */ jsx3(StatusRow, { label: "Project ID:", value: linkedProject.projectId }),
121
- /* @__PURE__ */ jsx3(StatusRow, { label: "Linked At:", value: new Date(linkedProject.linkedAt).toLocaleString() })
147
+ /* @__PURE__ */ jsx3(
148
+ StatusRow,
149
+ {
150
+ label: "Linked At:",
151
+ value: new Date(linkedProject.linkedAt).toLocaleString()
152
+ }
153
+ )
122
154
  ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
123
- /* @__PURE__ */ jsx3(StatusRow, { label: "Status:", value: /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "Not linked" }) }),
155
+ /* @__PURE__ */ jsx3(
156
+ StatusRow,
157
+ {
158
+ label: "Status:",
159
+ value: /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "Not linked" })
160
+ }
161
+ ),
124
162
  /* @__PURE__ */ jsx3(Text3, { color: "gray", children: 'Run "stint link" to link this directory to a project.' })
125
163
  ] }) }),
126
164
  /* @__PURE__ */ jsx3(Panel, { title: "Git Repository", icon: "\u{1F4C2}", children: isRepo && repoInfo ? /* @__PURE__ */ jsxs3(Fragment, { children: [
127
- /* @__PURE__ */ jsx3(StatusRow, { label: "Branch:", value: /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: repoInfo.currentBranch }) }),
128
- /* @__PURE__ */ jsx3(StatusRow, { label: "Remote:", value: repoInfo.remoteUrl || /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "None" }) }),
165
+ /* @__PURE__ */ jsx3(
166
+ StatusRow,
167
+ {
168
+ label: "Branch:",
169
+ value: /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: repoInfo.currentBranch })
170
+ }
171
+ ),
172
+ /* @__PURE__ */ jsx3(
173
+ StatusRow,
174
+ {
175
+ label: "Remote:",
176
+ value: repoInfo.remoteUrl || /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "None" })
177
+ }
178
+ ),
129
179
  /* @__PURE__ */ jsx3(
130
180
  StatusRow,
131
181
  {
@@ -136,18 +186,42 @@ function StatusDashboard({ cwd }) {
136
186
  renderGitChanges(repoInfo)
137
187
  ] }) : /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "Not a git repository" }) }),
138
188
  /* @__PURE__ */ jsx3(Panel, { title: "Authentication", icon: "\u{1F510}", children: user ? /* @__PURE__ */ jsxs3(Fragment, { children: [
139
- /* @__PURE__ */ jsx3(StatusRow, { label: "Status:", value: /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 Authenticated" }) }),
189
+ /* @__PURE__ */ jsx3(
190
+ StatusRow,
191
+ {
192
+ label: "Status:",
193
+ value: /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 Authenticated" })
194
+ }
195
+ ),
140
196
  /* @__PURE__ */ jsx3(StatusRow, { label: "User:", value: `${user.name} (${user.email})` }),
141
197
  /* @__PURE__ */ jsx3(StatusRow, { label: "Machine:", value: authService.getMachineName() })
142
198
  ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
143
- /* @__PURE__ */ jsx3(StatusRow, { label: "Status:", value: /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "Not logged in" }) }),
199
+ /* @__PURE__ */ jsx3(
200
+ StatusRow,
201
+ {
202
+ label: "Status:",
203
+ value: /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "Not logged in" })
204
+ }
205
+ ),
144
206
  /* @__PURE__ */ jsx3(Text3, { color: "gray", children: 'Run "stint login" to authenticate.' })
145
207
  ] }) }),
146
208
  /* @__PURE__ */ jsx3(Panel, { title: "Daemon", icon: "\u2699\uFE0F", children: daemonRunning ? /* @__PURE__ */ jsxs3(Fragment, { children: [
147
- /* @__PURE__ */ jsx3(StatusRow, { label: "Status:", value: /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 Running" }) }),
209
+ /* @__PURE__ */ jsx3(
210
+ StatusRow,
211
+ {
212
+ label: "Status:",
213
+ value: /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 Running" })
214
+ }
215
+ ),
148
216
  /* @__PURE__ */ jsx3(StatusRow, { label: "PID:", value: String(daemonPid) })
149
217
  ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
150
- /* @__PURE__ */ jsx3(StatusRow, { label: "Status:", value: /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "Not running" }) }),
218
+ /* @__PURE__ */ jsx3(
219
+ StatusRow,
220
+ {
221
+ label: "Status:",
222
+ value: /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "Not running" })
223
+ }
224
+ ),
151
225
  /* @__PURE__ */ jsx3(Text3, { color: "gray", children: 'Run "stint daemon start" to start the background agent.' })
152
226
  ] }) }),
153
227
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
@@ -161,9 +235,9 @@ function StatusDashboard({ cwd }) {
161
235
  ] }),
162
236
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
163
237
  /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "[q]" }),
164
- /* @__PURE__ */ jsx3(Text3, { children: " Quit " }),
238
+ /* @__PURE__ */ jsx3(Text3, { children: " Quit " }),
165
239
  /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "[r]" }),
166
- /* @__PURE__ */ jsx3(Text3, { children: " Refresh " }),
240
+ /* @__PURE__ */ jsx3(Text3, { children: " Refresh " }),
167
241
  /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "[?]" }),
168
242
  /* @__PURE__ */ jsx3(Text3, { children: " Help" })
169
243
  ] })
@@ -178,9 +252,9 @@ function StatusDashboard({ cwd }) {
178
252
  padding: 1,
179
253
  children: [
180
254
  /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Keyboard Shortcuts" }),
181
- /* @__PURE__ */ jsx3(Text3, { children: " q, Esc - Exit dashboard" }),
182
- /* @__PURE__ */ jsx3(Text3, { children: " r - Refresh status immediately" }),
183
- /* @__PURE__ */ jsx3(Text3, { children: " ? - Toggle this help" })
255
+ /* @__PURE__ */ jsx3(Text3, { children: " q, Esc - Exit dashboard" }),
256
+ /* @__PURE__ */ jsx3(Text3, { children: " r - Refresh status immediately" }),
257
+ /* @__PURE__ */ jsx3(Text3, { children: " ? - Toggle this help" })
184
258
  ]
185
259
  }
186
260
  )
@@ -202,18 +276,24 @@ function renderGitChanges(repoInfo) {
202
276
  }
203
277
  ),
204
278
  staged.length > 0 && /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color: "green", children: [
205
- "Staged: ",
279
+ "Staged: ",
206
280
  staged.length
207
281
  ] }) }),
208
282
  unstaged.length > 0 && /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
209
- "Unstaged: ",
283
+ "Unstaged: ",
210
284
  unstaged.length
211
285
  ] }) }),
212
286
  untracked.length > 0 && /* @__PURE__ */ jsx3(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
213
287
  "Untracked: ",
214
288
  untracked.length
215
289
  ] }) })
216
- ] }) : /* @__PURE__ */ jsx3(StatusRow, { label: "Changes:", value: /* @__PURE__ */ jsx3(Text3, { color: "green", children: "Clean working tree" }) }),
290
+ ] }) : /* @__PURE__ */ jsx3(
291
+ StatusRow,
292
+ {
293
+ label: "Changes:",
294
+ value: /* @__PURE__ */ jsx3(Text3, { color: "green", children: "Clean working tree" })
295
+ }
296
+ ),
217
297
  (ahead > 0 || behind > 0) && /* @__PURE__ */ jsx3(
218
298
  StatusRow,
219
299
  {
@@ -0,0 +1,7 @@
1
+ import {
2
+ apiService
3
+ } from "./chunk-IBGWKTT7.js";
4
+ import "./chunk-HPHXBSGB.js";
5
+ export {
6
+ apiService
7
+ };
@@ -44,7 +44,13 @@ var DEFAULT_CONFIG = {
44
44
  reverbAppKey: "wtn6tu6lirfv6yflujk7",
45
45
  projects: {},
46
46
  notifications: {
47
- enabled: true
47
+ enabled: true,
48
+ commits: true,
49
+ // Critical - on by default
50
+ sync: false,
51
+ // Noisy - off by default
52
+ suggestions: true
53
+ // Actionable - on by default
48
54
  }
49
55
  };
50
56
  var ConfigManager = class {
@@ -164,8 +170,33 @@ var ConfigManager = class {
164
170
  const notifConfig = this.conf.get("notifications");
165
171
  return notifConfig?.enabled ?? true;
166
172
  }
173
+ /**
174
+ * Check if a specific notification category is enabled.
175
+ * Returns false if master toggle is off, regardless of category setting.
176
+ */
177
+ isCategoryEnabled(category) {
178
+ if (!this.areNotificationsEnabled()) {
179
+ return false;
180
+ }
181
+ const notifConfig = this.conf.get("notifications");
182
+ switch (category) {
183
+ case "commits":
184
+ return notifConfig?.commits ?? true;
185
+ case "sync":
186
+ return notifConfig?.sync ?? false;
187
+ case "suggestions":
188
+ return notifConfig?.suggestions ?? true;
189
+ default:
190
+ return true;
191
+ }
192
+ }
167
193
  setNotificationsEnabled(enabled) {
168
- this.conf.set("notifications", { enabled });
194
+ const current = this.conf.get("notifications") || {};
195
+ this.conf.set("notifications", { ...current, enabled });
196
+ }
197
+ setNotificationCategory(category, enabled) {
198
+ const current = this.conf.get("notifications") || { enabled: true };
199
+ this.conf.set("notifications", { ...current, [category]: enabled });
169
200
  }
170
201
  };
171
202
  var config = new ConfigManager();
@@ -315,7 +346,7 @@ var AuthServiceImpl = class {
315
346
  return null;
316
347
  }
317
348
  try {
318
- const { apiService } = await import("./api-Z3HF3YU7.js");
349
+ const { apiService } = await import("./api-AFSILC7K.js");
319
350
  const user = await apiService.getCurrentUser();
320
351
  logger.info("auth", `Token validated for user: ${user.email}`);
321
352
  return user;
@@ -2,7 +2,7 @@ import {
2
2
  authService,
3
3
  config,
4
4
  logger
5
- } from "./chunk-RC7Z6GTK.js";
5
+ } from "./chunk-HPHXBSGB.js";
6
6
 
7
7
  // src/utils/circuit-breaker.ts
8
8
  var CircuitBreaker = class {
@@ -61,7 +61,9 @@ var CircuitBreaker = class {
61
61
  this.lastFailureTime = Date.now();
62
62
  this.failureTimestamps.push(this.lastFailureTime);
63
63
  const windowStart = this.lastFailureTime - this.options.windowSize;
64
- this.failureTimestamps = this.failureTimestamps.filter((ts) => ts > windowStart);
64
+ this.failureTimestamps = this.failureTimestamps.filter(
65
+ (ts) => ts > windowStart
66
+ );
65
67
  this.failures = this.failureTimestamps.length;
66
68
  if (this.state === "HALF_OPEN") {
67
69
  this.state = "OPEN";
@@ -98,7 +100,7 @@ var CircuitBreaker = class {
98
100
  };
99
101
 
100
102
  // src/services/api.ts
101
- var AGENT_VERSION = "1.2.35";
103
+ var AGENT_VERSION = "1.2.37";
102
104
  var ApiServiceImpl = class {
103
105
  sessionId = null;
104
106
  circuitBreaker = new CircuitBreaker({
@@ -109,6 +111,16 @@ var ApiServiceImpl = class {
109
111
  windowSize: 6e4
110
112
  // 60s failure window
111
113
  });
114
+ /**
115
+ * Sync notification preferences from server to local config
116
+ */
117
+ syncNotificationPreferences(prefs) {
118
+ config.setNotificationsEnabled(prefs.enabled);
119
+ config.setNotificationCategory("commits", prefs.commits);
120
+ config.setNotificationCategory("sync", prefs.sync);
121
+ config.setNotificationCategory("suggestions", prefs.suggestions);
122
+ logger.debug("api", "Synced notification preferences from server");
123
+ }
112
124
  /**
113
125
  * Get authentication headers for API requests
114
126
  * @returns Headers object with Authorization and Content-Type
@@ -117,12 +129,14 @@ var ApiServiceImpl = class {
117
129
  async getHeaders() {
118
130
  const token = await authService.getToken();
119
131
  if (!token) {
120
- throw new Error('No authentication token found. Please run "stint login" first.');
132
+ throw new Error(
133
+ 'No authentication token found. Please run "stint login" first.'
134
+ );
121
135
  }
122
136
  return {
123
137
  Authorization: `Bearer ${token}`,
124
138
  "Content-Type": "application/json",
125
- "Accept": "application/json"
139
+ Accept: "application/json"
126
140
  };
127
141
  }
128
142
  /**
@@ -146,7 +160,9 @@ var ApiServiceImpl = class {
146
160
  });
147
161
  if (!response.ok) {
148
162
  const errorText = await response.text();
149
- throw new Error(`API request failed: ${response.status} ${errorText}`);
163
+ throw new Error(
164
+ `API request failed: ${response.status} ${errorText}`
165
+ );
150
166
  }
151
167
  return await response.json();
152
168
  } catch (error) {
@@ -175,10 +191,16 @@ var ApiServiceImpl = class {
175
191
  }
176
192
  if (attempt < maxRetries) {
177
193
  const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 5e3);
178
- logger.warn("api", `${operationName} failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`);
194
+ logger.warn(
195
+ "api",
196
+ `${operationName} failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`
197
+ );
179
198
  await new Promise((resolve) => setTimeout(resolve, delay));
180
199
  } else {
181
- logger.error("api", `${operationName} failed after ${maxRetries} attempts`);
200
+ logger.error(
201
+ "api",
202
+ `${operationName} failed after ${maxRetries} attempts`
203
+ );
182
204
  }
183
205
  }
184
206
  }
@@ -204,6 +226,9 @@ var ApiServiceImpl = class {
204
226
  });
205
227
  const session = response.data;
206
228
  this.sessionId = session.id;
229
+ if (response.notification_preferences) {
230
+ this.syncNotificationPreferences(response.notification_preferences);
231
+ }
207
232
  logger.success("api", `Agent session connected: ${session.id}`);
208
233
  return session;
209
234
  }, "Connect");
@@ -236,12 +261,15 @@ var ApiServiceImpl = class {
236
261
  throw new Error("No active session");
237
262
  }
238
263
  await this.withRetry(async () => {
239
- await this.request("/api/agent/heartbeat", {
264
+ const response = await this.request("/api/agent/heartbeat", {
240
265
  method: "POST",
241
266
  body: JSON.stringify({
242
267
  session_id: this.sessionId
243
268
  })
244
269
  });
270
+ if (response.notification_preferences) {
271
+ this.syncNotificationPreferences(response.notification_preferences);
272
+ }
245
273
  logger.debug("api", "Heartbeat sent");
246
274
  }, "Heartbeat");
247
275
  }
@@ -259,7 +287,9 @@ var ApiServiceImpl = class {
259
287
  const projectId2 = item.project_id || item.projectId;
260
288
  const createdAt = item.created_at || item.createdAt;
261
289
  if (!projectId2 || !createdAt) {
262
- throw new Error(`Invalid commit data received from API: ${JSON.stringify(item)}`);
290
+ throw new Error(
291
+ `Invalid commit data received from API: ${JSON.stringify(item)}`
292
+ );
263
293
  }
264
294
  return {
265
295
  id: item.id,
@@ -279,13 +309,17 @@ var ApiServiceImpl = class {
279
309
  */
280
310
  async getCommit(commitId) {
281
311
  logger.info("api", `Fetching commit ${commitId}`);
282
- const response = await this.request(`/api/agent/commits/${commitId}`);
312
+ const response = await this.request(
313
+ `/api/agent/commits/${commitId}`
314
+ );
283
315
  const item = response.data;
284
316
  const projectId = item.project_id || item.projectId;
285
317
  const createdAt = item.created_at || item.createdAt;
286
318
  const hasLargeFiles = item.has_large_files || item.hasLargeFiles;
287
319
  if (!projectId || !createdAt) {
288
- throw new Error(`Invalid commit data received from API: ${JSON.stringify(item)}`);
320
+ throw new Error(
321
+ `Invalid commit data received from API: ${JSON.stringify(item)}`
322
+ );
289
323
  }
290
324
  return {
291
325
  id: item.id,
@@ -305,7 +339,10 @@ var ApiServiceImpl = class {
305
339
  * @returns Updated commit data
306
340
  */
307
341
  async markCommitExecuted(commitId, sha, pushed = true, pushError) {
308
- logger.info("api", `Marking commit ${commitId} as executed (SHA: ${sha}, pushed: ${pushed})`);
342
+ logger.info(
343
+ "api",
344
+ `Marking commit ${commitId} as executed (SHA: ${sha}, pushed: ${pushed})`
345
+ );
309
346
  return this.withRetry(async () => {
310
347
  const response = await this.request(
311
348
  `/api/agent/commits/${commitId}/executed`,
@@ -319,7 +356,9 @@ var ApiServiceImpl = class {
319
356
  const createdAt = data.created_at || data.createdAt;
320
357
  const executedAt = data.executed_at || data.executedAt;
321
358
  if (!projectId || !createdAt) {
322
- throw new Error(`Invalid commit data received from API: ${JSON.stringify(data)}`);
359
+ throw new Error(
360
+ `Invalid commit data received from API: ${JSON.stringify(data)}`
361
+ );
323
362
  }
324
363
  const commit = {
325
364
  id: data.id,
@@ -373,7 +412,10 @@ var ApiServiceImpl = class {
373
412
  method: "POST",
374
413
  body: JSON.stringify(payload)
375
414
  });
376
- logger.success("api", `Project ${projectId} synced (${changedFiles?.length ?? 0} changed files)`);
415
+ logger.success(
416
+ "api",
417
+ `Project ${projectId} synced (${changedFiles?.length ?? 0} changed files)`
418
+ );
377
419
  return {
378
420
  auto_sync: response.auto_sync
379
421
  };
@@ -391,7 +433,9 @@ var ApiServiceImpl = class {
391
433
  }
392
434
  async getLinkedProjects() {
393
435
  logger.info("api", "Fetching linked projects");
394
- const response = await this.request("/api/agent/projects");
436
+ const response = await this.request(
437
+ "/api/agent/projects"
438
+ );
395
439
  const projects = response.data;
396
440
  logger.info("api", `Found ${projects.length} linked projects`);
397
441
  return projects;
@@ -407,10 +451,13 @@ var ApiServiceImpl = class {
407
451
  */
408
452
  async createProject(data) {
409
453
  logger.info("api", `Creating project: ${data.name}`);
410
- const response = await this.request("/api/agent/projects", {
411
- method: "POST",
412
- body: JSON.stringify(data)
413
- });
454
+ const response = await this.request(
455
+ "/api/agent/projects",
456
+ {
457
+ method: "POST",
458
+ body: JSON.stringify(data)
459
+ }
460
+ );
414
461
  const project = response.data;
415
462
  logger.success("api", `Created project: ${project.name} (${project.id})`);
416
463
  return project;