@quanta-intellect/vessel-browser 0.1.16 → 0.1.18

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/out/main/index.js CHANGED
@@ -26,7 +26,15 @@ const defaults = {
26
26
  chatProvider: null,
27
27
  maxToolIterations: 200,
28
28
  domainPolicy: { allowedDomains: [], blockedDomains: [] },
29
- downloadPath: ""
29
+ downloadPath: "",
30
+ telemetryEnabled: true,
31
+ premium: {
32
+ status: "free",
33
+ customerId: "",
34
+ email: "",
35
+ validatedAt: "",
36
+ expiresAt: ""
37
+ }
30
38
  };
31
39
  const SETTABLE_KEYS = new Set(Object.keys(defaults));
32
40
  let settings = null;
@@ -80,12 +88,9 @@ function loadSettings() {
80
88
  return settings;
81
89
  }
82
90
  function saveSettings() {
83
- try {
84
- fs.mkdirSync(path.dirname(getSettingsPath()), { recursive: true });
85
- fs.writeFileSync(getSettingsPath(), JSON.stringify(settings, null, 2));
86
- } catch (err) {
87
- console.error("[Vessel] Failed to save settings:", err);
88
- }
91
+ fs.promises.mkdir(path.dirname(getSettingsPath()), { recursive: true }).then(
92
+ () => fs.promises.writeFile(getSettingsPath(), JSON.stringify(settings, null, 2))
93
+ ).catch((err) => console.error("[Vessel] Failed to save settings:", err));
89
94
  }
90
95
  function setSetting(key, value) {
91
96
  loadSettings();
@@ -528,11 +533,7 @@ function load$2() {
528
533
  return state$3;
529
534
  }
530
535
  function save$2() {
531
- try {
532
- fs.writeFileSync(getHighlightsPath(), JSON.stringify(state$3, null, 2), "utf-8");
533
- } catch (err) {
534
- console.error("[Vessel] Failed to save highlights:", err);
535
- }
536
+ fs.promises.writeFile(getHighlightsPath(), JSON.stringify(state$3, null, 2), "utf-8").catch((err) => console.error("[Vessel] Failed to save highlights:", err));
536
537
  }
537
538
  function emit$2() {
538
539
  if (!state$3) return;
@@ -1202,16 +1203,13 @@ function load$1() {
1202
1203
  return state$2;
1203
1204
  }
1204
1205
  function save$1() {
1205
- try {
1206
- fs.mkdirSync(path.dirname(getHistoryPath()), { recursive: true });
1207
- fs.writeFileSync(
1206
+ fs.promises.mkdir(path.dirname(getHistoryPath()), { recursive: true }).then(
1207
+ () => fs.promises.writeFile(
1208
1208
  getHistoryPath(),
1209
1209
  JSON.stringify(state$2, null, 2),
1210
1210
  "utf-8"
1211
- );
1212
- } catch (err) {
1213
- console.error("[Vessel] Failed to save history:", err);
1214
- }
1211
+ )
1212
+ ).catch((err) => console.error("[Vessel] Failed to save history:", err));
1215
1213
  }
1216
1214
  function emit$1() {
1217
1215
  if (!state$2) return;
@@ -2344,6 +2342,13 @@ const Channels = {
2344
2342
  DOWNLOAD_STARTED: "download:started",
2345
2343
  DOWNLOAD_PROGRESS: "download:progress",
2346
2344
  DOWNLOAD_DONE: "download:done",
2345
+ // Premium
2346
+ PREMIUM_GET_STATE: "premium:get-state",
2347
+ PREMIUM_ACTIVATE: "premium:activate",
2348
+ PREMIUM_CHECKOUT: "premium:checkout",
2349
+ PREMIUM_PORTAL: "premium:portal",
2350
+ PREMIUM_RESET: "premium:reset",
2351
+ PREMIUM_UPDATE: "premium:update",
2347
2352
  // Window controls
2348
2353
  WINDOW_MINIMIZE: "window:minimize",
2349
2354
  WINDOW_MAXIMIZE: "window:maximize",
@@ -4406,6 +4411,288 @@ function setMcpHealth(update) {
4406
4411
  }
4407
4412
  }
4408
4413
  }
4414
+ const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
4415
+ const FREE_TOOL_ITERATION_LIMIT = 50;
4416
+ const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
4417
+ const OFFLINE_GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1e3;
4418
+ const PREMIUM_TOOLS = /* @__PURE__ */ new Set([
4419
+ "screenshot",
4420
+ "save_session",
4421
+ "load_session",
4422
+ "list_sessions",
4423
+ "delete_session",
4424
+ "flow_start",
4425
+ "flow_advance",
4426
+ "flow_status",
4427
+ "flow_end",
4428
+ "metrics",
4429
+ "extract_table"
4430
+ ]);
4431
+ function isPremium() {
4432
+ const { premium } = loadSettings();
4433
+ if (premium.status === "active" || premium.status === "trialing") {
4434
+ return true;
4435
+ }
4436
+ if (premium.validatedAt && premium.status !== "free") {
4437
+ const lastValidated = new Date(premium.validatedAt).getTime();
4438
+ if (Date.now() - lastValidated < OFFLINE_GRACE_PERIOD_MS) {
4439
+ return true;
4440
+ }
4441
+ }
4442
+ return false;
4443
+ }
4444
+ function getPremiumState() {
4445
+ return { ...loadSettings().premium };
4446
+ }
4447
+ function getEffectiveMaxIterations() {
4448
+ if (isPremium()) {
4449
+ return loadSettings().maxToolIterations || 200;
4450
+ }
4451
+ return FREE_TOOL_ITERATION_LIMIT;
4452
+ }
4453
+ function resetPremium() {
4454
+ const fresh = {
4455
+ status: "free",
4456
+ customerId: "",
4457
+ email: "",
4458
+ validatedAt: "",
4459
+ expiresAt: ""
4460
+ };
4461
+ setSetting("premium", fresh);
4462
+ return fresh;
4463
+ }
4464
+ function isToolGated(toolName) {
4465
+ return PREMIUM_TOOLS.has(toolName) && !isPremium();
4466
+ }
4467
+ async function getCheckoutUrl(email) {
4468
+ try {
4469
+ const params = new URLSearchParams();
4470
+ if (email) params.set("email", email);
4471
+ const res = await fetch(`${VERIFICATION_API}/checkout?${params}`, {
4472
+ method: "POST",
4473
+ headers: { "Content-Type": "application/json" }
4474
+ });
4475
+ if (!res.ok) {
4476
+ const body = await res.text();
4477
+ return { ok: false, error: body || `HTTP ${res.status}` };
4478
+ }
4479
+ const { url } = await res.json();
4480
+ return { ok: true, url };
4481
+ } catch (err) {
4482
+ return {
4483
+ ok: false,
4484
+ error: err instanceof Error ? err.message : "Failed to create checkout"
4485
+ };
4486
+ }
4487
+ }
4488
+ async function getPortalUrl() {
4489
+ const { premium } = loadSettings();
4490
+ if (!premium.customerId) {
4491
+ return { ok: false, error: "No active subscription" };
4492
+ }
4493
+ try {
4494
+ const res = await fetch(`${VERIFICATION_API}/portal`, {
4495
+ method: "POST",
4496
+ headers: { "Content-Type": "application/json" },
4497
+ body: JSON.stringify({ customerId: premium.customerId })
4498
+ });
4499
+ if (!res.ok) {
4500
+ return { ok: false, error: `HTTP ${res.status}` };
4501
+ }
4502
+ const { url } = await res.json();
4503
+ return { ok: true, url };
4504
+ } catch (err) {
4505
+ return {
4506
+ ok: false,
4507
+ error: err instanceof Error ? err.message : "Failed to get portal URL"
4508
+ };
4509
+ }
4510
+ }
4511
+ async function verifySubscription(emailOrCustomerId) {
4512
+ const current = loadSettings().premium;
4513
+ const identifier = emailOrCustomerId || current.customerId || current.email;
4514
+ if (!identifier) {
4515
+ return current;
4516
+ }
4517
+ try {
4518
+ const res = await fetch(`${VERIFICATION_API}/verify`, {
4519
+ method: "POST",
4520
+ headers: { "Content-Type": "application/json" },
4521
+ body: JSON.stringify({ identifier })
4522
+ });
4523
+ if (!res.ok) {
4524
+ console.warn("[Vessel Premium] Verification API returned", res.status);
4525
+ return current;
4526
+ }
4527
+ const data = await res.json();
4528
+ const updated = {
4529
+ status: data.status,
4530
+ customerId: data.customerId,
4531
+ email: data.email,
4532
+ validatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4533
+ expiresAt: data.expiresAt
4534
+ };
4535
+ setSetting("premium", updated);
4536
+ return updated;
4537
+ } catch (err) {
4538
+ console.warn("[Vessel Premium] Verification failed:", err);
4539
+ return current;
4540
+ }
4541
+ }
4542
+ async function activateWithEmail(email) {
4543
+ if (!email.trim()) {
4544
+ return { ok: false, state: getPremiumState(), error: "Email is required" };
4545
+ }
4546
+ const state2 = await verifySubscription(email.trim());
4547
+ if (state2.status === "active" || state2.status === "trialing") {
4548
+ return { ok: true, state: state2 };
4549
+ }
4550
+ return {
4551
+ ok: false,
4552
+ state: state2,
4553
+ error: state2.status === "canceled" ? "Subscription is canceled. Resubscribe to continue." : state2.status === "past_due" ? "Subscription payment is past due. Update your payment method." : "No active subscription found for this email."
4554
+ };
4555
+ }
4556
+ let revalidationTimer = null;
4557
+ function startBackgroundRevalidation() {
4558
+ if (revalidationTimer) return;
4559
+ const { premium } = loadSettings();
4560
+ if (premium.customerId || premium.email) {
4561
+ const lastValidated = premium.validatedAt ? new Date(premium.validatedAt).getTime() : 0;
4562
+ if (Date.now() - lastValidated > REVALIDATION_INTERVAL_MS) {
4563
+ void verifySubscription();
4564
+ }
4565
+ }
4566
+ revalidationTimer = setInterval(() => {
4567
+ const { premium: p } = loadSettings();
4568
+ if (p.customerId || p.email) {
4569
+ void verifySubscription();
4570
+ }
4571
+ }, REVALIDATION_INTERVAL_MS);
4572
+ }
4573
+ function stopBackgroundRevalidation() {
4574
+ if (revalidationTimer) {
4575
+ clearInterval(revalidationTimer);
4576
+ revalidationTimer = null;
4577
+ }
4578
+ }
4579
+ const POSTHOG_API_KEY = process.env.POSTHOG_API_KEY || "phc_OMeM3P5cxJwl14lOKxYad0Yre52xvjNfkLEFnPtXyM";
4580
+ const POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
4581
+ const BATCH_INTERVAL_MS = 6e4;
4582
+ const MAX_BATCH_SIZE = 50;
4583
+ function getDeviceIdPath() {
4584
+ return path.join(electron.app.getPath("userData"), ".vessel-device-id");
4585
+ }
4586
+ let deviceId = null;
4587
+ function getDeviceId() {
4588
+ if (deviceId) return deviceId;
4589
+ const idPath = getDeviceIdPath();
4590
+ try {
4591
+ deviceId = fs.readFileSync(idPath, "utf-8").trim();
4592
+ if (deviceId) return deviceId;
4593
+ } catch {
4594
+ }
4595
+ deviceId = crypto.randomUUID();
4596
+ try {
4597
+ fs.mkdirSync(path.dirname(idPath), { recursive: true });
4598
+ fs.writeFileSync(idPath, deviceId, "utf-8");
4599
+ } catch {
4600
+ }
4601
+ return deviceId;
4602
+ }
4603
+ let eventQueue = [];
4604
+ let flushTimer = null;
4605
+ let sessionStartedAt = null;
4606
+ function isEnabled() {
4607
+ if (POSTHOG_API_KEY === "YOUR_POSTHOG_KEY_HERE") return false;
4608
+ return loadSettings().telemetryEnabled !== false;
4609
+ }
4610
+ function trackEvent(event, properties = {}) {
4611
+ if (!isEnabled()) return;
4612
+ eventQueue.push({
4613
+ event,
4614
+ properties: {
4615
+ ...properties,
4616
+ premium_status: isPremium() ? "premium" : "free",
4617
+ app_version: electron.app.getVersion(),
4618
+ platform: process.platform,
4619
+ arch: process.arch
4620
+ },
4621
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4622
+ });
4623
+ if (eventQueue.length >= MAX_BATCH_SIZE) {
4624
+ void flush();
4625
+ }
4626
+ }
4627
+ function trackToolCall(toolName, pageType) {
4628
+ trackEvent("tool_called", {
4629
+ tool_name: toolName,
4630
+ page_type: "unknown"
4631
+ });
4632
+ }
4633
+ function trackProviderConfigured(providerId) {
4634
+ trackEvent("provider_configured", {
4635
+ provider_id: providerId
4636
+ });
4637
+ }
4638
+ function startTelemetry() {
4639
+ if (!isEnabled()) return;
4640
+ sessionStartedAt = Date.now();
4641
+ trackEvent("app_launched", {
4642
+ electron_version: process.versions.electron,
4643
+ chrome_version: process.versions.chrome
4644
+ });
4645
+ flushTimer = setInterval(() => {
4646
+ void flush();
4647
+ }, BATCH_INTERVAL_MS);
4648
+ }
4649
+ function stopTelemetry() {
4650
+ if (sessionStartedAt) {
4651
+ const durationMinutes = Math.round(
4652
+ (Date.now() - sessionStartedAt) / 6e4
4653
+ );
4654
+ trackEvent("app_session_ended", {
4655
+ duration_minutes: durationMinutes
4656
+ });
4657
+ sessionStartedAt = null;
4658
+ }
4659
+ if (flushTimer) {
4660
+ clearInterval(flushTimer);
4661
+ flushTimer = null;
4662
+ }
4663
+ void flush();
4664
+ }
4665
+ async function flush() {
4666
+ if (eventQueue.length === 0) return;
4667
+ if (!isEnabled()) {
4668
+ eventQueue = [];
4669
+ return;
4670
+ }
4671
+ const batch = eventQueue.splice(0);
4672
+ const distinctId = getDeviceId();
4673
+ const payload = {
4674
+ api_key: POSTHOG_API_KEY,
4675
+ batch: batch.map((e) => ({
4676
+ event: e.event,
4677
+ properties: {
4678
+ distinct_id: distinctId,
4679
+ ...e.properties
4680
+ },
4681
+ timestamp: e.timestamp
4682
+ }))
4683
+ };
4684
+ try {
4685
+ await fetch(`${POSTHOG_HOST}/batch`, {
4686
+ method: "POST",
4687
+ headers: { "Content-Type": "application/json" },
4688
+ body: JSON.stringify(payload)
4689
+ });
4690
+ } catch {
4691
+ if (eventQueue.length < MAX_BATCH_SIZE * 2) {
4692
+ eventQueue.unshift(...batch);
4693
+ }
4694
+ }
4695
+ }
4409
4696
  function isRichToolResult(value) {
4410
4697
  return typeof value === "object" && value !== null && value.__richResult === true;
4411
4698
  }
@@ -4419,7 +4706,6 @@ function makeImageResult(base64, description, mediaType = "image/png") {
4419
4706
  };
4420
4707
  return JSON.stringify(result);
4421
4708
  }
4422
- const DEFAULT_MAX_ITERATIONS$1 = 200;
4423
4709
  class AnthropicProvider {
4424
4710
  client;
4425
4711
  model;
@@ -4467,7 +4753,7 @@ class AnthropicProvider {
4467
4753
  { role: "user", content: userMessage }
4468
4754
  ];
4469
4755
  try {
4470
- const maxIterations = loadSettings().maxToolIterations || DEFAULT_MAX_ITERATIONS$1;
4756
+ const maxIterations = getEffectiveMaxIterations();
4471
4757
  let iterationsUsed = 0;
4472
4758
  for (let i = 0; i < maxIterations; i++) {
4473
4759
  iterationsUsed = i + 1;
@@ -4708,7 +4994,6 @@ const PROVIDERS = {
4708
4994
  apiKeyHint: "Any OpenAI-compatible API endpoint"
4709
4995
  }
4710
4996
  };
4711
- const DEFAULT_MAX_ITERATIONS = 200;
4712
4997
  function toOpenAITools(tools) {
4713
4998
  return tools.map((t) => ({
4714
4999
  type: "function",
@@ -4774,7 +5059,7 @@ class OpenAICompatProvider {
4774
5059
  { role: "user", content: userMessage }
4775
5060
  ];
4776
5061
  try {
4777
- const maxIterations = loadSettings().maxToolIterations || DEFAULT_MAX_ITERATIONS;
5062
+ const maxIterations = getEffectiveMaxIterations();
4778
5063
  let iterationsUsed = 0;
4779
5064
  for (let i = 0; i < maxIterations; i++) {
4780
5065
  iterationsUsed = i + 1;
@@ -7285,7 +7570,7 @@ function pruneToolsForContext(tools, pageType, query = "") {
7285
7570
  const ctx = pageType ?? "GENERAL";
7286
7571
  const hints = CONTEXT_HINTS[ctx] ?? {};
7287
7572
  const intents = inferIntent(query);
7288
- const scored = tools.filter((tool) => shouldIncludeTool(tool.name, ctx, intents)).map((tool) => ({
7573
+ const scored = tools.filter((tool) => !isToolGated(tool.name)).filter((tool) => shouldIncludeTool(tool.name, ctx, intents)).map((tool) => ({
7289
7574
  tool,
7290
7575
  score: scoreForContext(tool.name, ctx)
7291
7576
  }));
@@ -7460,12 +7745,13 @@ function load() {
7460
7745
  return state;
7461
7746
  }
7462
7747
  function save() {
7463
- try {
7464
- fs.mkdirSync(path.dirname(getBookmarksPath()), { recursive: true });
7465
- fs.writeFileSync(getBookmarksPath(), JSON.stringify(state, null, 2), "utf-8");
7466
- } catch (err) {
7467
- console.error("[Vessel] Failed to save bookmarks:", err);
7468
- }
7748
+ fs.promises.mkdir(path.dirname(getBookmarksPath()), { recursive: true }).then(
7749
+ () => fs.promises.writeFile(
7750
+ getBookmarksPath(),
7751
+ JSON.stringify(state, null, 2),
7752
+ "utf-8"
7753
+ )
7754
+ ).catch((err) => console.error("[Vessel] Failed to save bookmarks:", err));
7469
7755
  }
7470
7756
  function emit() {
7471
7757
  if (!state) return;
@@ -10782,6 +11068,10 @@ async function executeAction(name, args, ctx) {
10782
11068
  ].includes(name)) {
10783
11069
  return "Error: No active tab";
10784
11070
  }
11071
+ trackToolCall(name);
11072
+ if (isToolGated(name)) {
11073
+ return `This tool (${name}) requires Vessel Premium. Upgrade at Settings > Premium to unlock screenshot, session management, workflow tracking, and more.`;
11074
+ }
10785
11075
  const wc = tab?.view.webContents;
10786
11076
  const result = await ctx.runtime.runControlledAction({
10787
11077
  source: "ai",
@@ -17236,15 +17526,18 @@ function startMcpServer(tabManager, runtime2, port) {
17236
17526
  res.end("Not found");
17237
17527
  return;
17238
17528
  }
17239
- res.setHeader("Access-Control-Allow-Origin", "null");
17240
- res.setHeader(
17241
- "Access-Control-Allow-Methods",
17242
- "POST, GET, DELETE, OPTIONS"
17243
- );
17244
- res.setHeader(
17245
- "Access-Control-Allow-Headers",
17246
- "Content-Type, mcp-session-id, Authorization"
17247
- );
17529
+ const origin = req.headers.origin;
17530
+ if (origin && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) {
17531
+ res.setHeader("Access-Control-Allow-Origin", origin);
17532
+ res.setHeader(
17533
+ "Access-Control-Allow-Methods",
17534
+ "POST, GET, DELETE, OPTIONS"
17535
+ );
17536
+ res.setHeader(
17537
+ "Access-Control-Allow-Headers",
17538
+ "Content-Type, mcp-session-id, Authorization"
17539
+ );
17540
+ }
17248
17541
  if (req.method === "OPTIONS") {
17249
17542
  res.writeHead(204);
17250
17543
  res.end();
@@ -17404,6 +17697,7 @@ function registerIpcHandlers(windowState, runtime2) {
17404
17697
  }
17405
17698
  try {
17406
17699
  activeChatProvider = createProvider(chatConfig);
17700
+ trackProviderConfigured(chatConfig.id);
17407
17701
  const activeTab = tabManager.getActiveTab();
17408
17702
  await handleAIQuery(
17409
17703
  query,
@@ -17704,6 +17998,35 @@ function registerIpcHandlers(windowState, runtime2) {
17704
17998
  layoutViews(windowState);
17705
17999
  return clamped;
17706
18000
  });
18001
+ electron.ipcMain.handle(Channels.PREMIUM_GET_STATE, () => {
18002
+ return getPremiumState();
18003
+ });
18004
+ electron.ipcMain.handle(Channels.PREMIUM_ACTIVATE, async (_, email) => {
18005
+ const result = await activateWithEmail(email);
18006
+ if (result.ok) {
18007
+ sendToRendererViews(Channels.PREMIUM_UPDATE, result.state);
18008
+ }
18009
+ return result;
18010
+ });
18011
+ electron.ipcMain.handle(Channels.PREMIUM_CHECKOUT, async (_, email) => {
18012
+ const result = await getCheckoutUrl(email);
18013
+ if (result.ok && result.url) {
18014
+ tabManager.createTab(result.url);
18015
+ }
18016
+ return result;
18017
+ });
18018
+ electron.ipcMain.handle(Channels.PREMIUM_RESET, () => {
18019
+ const state2 = resetPremium();
18020
+ sendToRendererViews(Channels.PREMIUM_UPDATE, state2);
18021
+ return state2;
18022
+ });
18023
+ electron.ipcMain.handle(Channels.PREMIUM_PORTAL, async () => {
18024
+ const result = await getPortalUrl();
18025
+ if (result.ok && result.url) {
18026
+ tabManager.createTab(result.url);
18027
+ }
18028
+ return result;
18029
+ });
17707
18030
  electron.ipcMain.handle(Channels.WINDOW_MINIMIZE, () => {
17708
18031
  mainWindow.minimize();
17709
18032
  });
@@ -18076,16 +18399,15 @@ ${progress}
18076
18399
  actions: this.state.actions.slice(-120),
18077
18400
  checkpoints: this.state.checkpoints.slice(-20)
18078
18401
  };
18079
- try {
18080
- fs$1.mkdirSync(path$1.dirname(getRuntimeStatePath()), { recursive: true });
18081
- fs$1.writeFileSync(
18402
+ return fs$1.promises.mkdir(path$1.dirname(getRuntimeStatePath()), { recursive: true }).then(
18403
+ () => fs$1.promises.writeFile(
18082
18404
  getRuntimeStatePath(),
18083
18405
  JSON.stringify(persisted, null, 2),
18084
18406
  "utf-8"
18085
- );
18086
- } catch (err) {
18087
- console.error("[Vessel] Failed to persist runtime state:", err);
18088
- }
18407
+ )
18408
+ ).catch(
18409
+ (err) => console.error("[Vessel] Failed to persist runtime state:", err)
18410
+ );
18089
18411
  }
18090
18412
  schedulePersist() {
18091
18413
  this.persistDirty = true;
@@ -18097,7 +18419,7 @@ ${progress}
18097
18419
  }
18098
18420
  /** Flush any pending debounced persist to disk immediately. Call on shutdown. */
18099
18421
  flushPersist() {
18100
- if (this.persistDirty) this.persistNow();
18422
+ return this.persistDirty ? this.persistNow() : Promise.resolve();
18101
18423
  }
18102
18424
  emit() {
18103
18425
  this.schedulePersist();
@@ -18473,6 +18795,8 @@ async function bootstrap() {
18473
18795
  sidebarView.webContents.send(Channels.HISTORY_UPDATE, state2);
18474
18796
  });
18475
18797
  installDownloadHandler(chromeView);
18798
+ startBackgroundRevalidation();
18799
+ startTelemetry();
18476
18800
  const chromeUrl = rendererUrlFor("chrome");
18477
18801
  const sidebarUrl = rendererUrlFor("sidebar");
18478
18802
  const devtoolsUrl = rendererUrlFor("devtools");
@@ -18512,6 +18836,8 @@ electron.app.whenReady().then(bootstrap).catch((error) => {
18512
18836
  });
18513
18837
  electron.app.on("window-all-closed", () => {
18514
18838
  electron.globalShortcut.unregisterAll();
18839
+ stopTelemetry();
18840
+ stopBackgroundRevalidation();
18515
18841
  runtime?.flushPersist();
18516
18842
  void stopMcpServer().finally(() => {
18517
18843
  electron.app.quit();
@@ -74,6 +74,13 @@ const Channels = {
74
74
  HISTORY_SEARCH: "history:search",
75
75
  HISTORY_CLEAR: "history:clear",
76
76
  HISTORY_UPDATE: "history:update",
77
+ // Premium
78
+ PREMIUM_GET_STATE: "premium:get-state",
79
+ PREMIUM_ACTIVATE: "premium:activate",
80
+ PREMIUM_CHECKOUT: "premium:checkout",
81
+ PREMIUM_PORTAL: "premium:portal",
82
+ PREMIUM_RESET: "premium:reset",
83
+ PREMIUM_UPDATE: "premium:update",
77
84
  // Window controls
78
85
  WINDOW_MINIMIZE: "window:minimize",
79
86
  WINDOW_MAXIMIZE: "window:maximize",
@@ -223,6 +230,18 @@ const api = {
223
230
  return () => electron.ipcRenderer.removeListener(Channels.HISTORY_UPDATE, handler);
224
231
  }
225
232
  },
233
+ premium: {
234
+ getState: () => electron.ipcRenderer.invoke(Channels.PREMIUM_GET_STATE),
235
+ activate: (email) => electron.ipcRenderer.invoke(Channels.PREMIUM_ACTIVATE, email),
236
+ checkout: (email) => electron.ipcRenderer.invoke(Channels.PREMIUM_CHECKOUT, email),
237
+ portal: () => electron.ipcRenderer.invoke(Channels.PREMIUM_PORTAL),
238
+ reset: () => electron.ipcRenderer.invoke(Channels.PREMIUM_RESET),
239
+ onUpdate: (cb) => {
240
+ const handler = (_, state) => cb(state);
241
+ electron.ipcRenderer.on(Channels.PREMIUM_UPDATE, handler);
242
+ return () => electron.ipcRenderer.removeListener(Channels.PREMIUM_UPDATE, handler);
243
+ }
244
+ },
226
245
  window: {
227
246
  minimize: () => electron.ipcRenderer.invoke(Channels.WINDOW_MINIMIZE),
228
247
  maximize: () => electron.ipcRenderer.invoke(Channels.WINDOW_MAXIMIZE),