claude-discover 1.10.5 → 1.10.6
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/bin/claude/statusline.mjs +1 -1
- package/out/main/main.js +180 -66
- package/out/preload/preload.cjs +1 -0
- package/out/renderer/assets/{index-BMh5255F.css → index-BQftbGlQ.css} +220 -36
- package/out/renderer/assets/{index-CHnuzPLr.js → index-CirBBtX2.js} +314 -80
- package/out/renderer/assets/{vendors-CpMM-Zb0.js → vendors-BRqWyPqt.js} +17 -7
- package/out/renderer/index.html +3 -3
- package/package.json +3 -3
|
@@ -10,7 +10,7 @@ import { styleText } from 'node:util'
|
|
|
10
10
|
// Formatting
|
|
11
11
|
const THRESHOLDS = { // (white, yellow, red)
|
|
12
12
|
context: [0, 100_000, 250_000],
|
|
13
|
-
limit: [0, 60, 90],
|
|
13
|
+
limit: [0, 60, 90], // keep in sync with src/renderer/utils/thresholds.js
|
|
14
14
|
cache: [90, 60, 0],
|
|
15
15
|
tokens: [0, 250_000, 1_000_000],
|
|
16
16
|
session: [0, 3_000_000, 8_000_000],
|
package/out/main/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { app, dialog, BrowserWindow,
|
|
1
|
+
import { app, shell, dialog, BrowserWindow, nativeTheme, Menu, ipcMain } from "electron";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
3
|
import os, { homedir } from "node:os";
|
|
4
4
|
import path, { join, dirname, basename } from "node:path";
|
|
@@ -6,12 +6,12 @@ import fs from "node:fs";
|
|
|
6
6
|
import { sync } from "write-file-atomic";
|
|
7
7
|
import fsp from "node:fs/promises";
|
|
8
8
|
import crypto from "node:crypto";
|
|
9
|
+
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
9
10
|
import which from "which";
|
|
10
11
|
import { execFile, spawn } from "node:child_process";
|
|
11
12
|
import { promisify } from "node:util";
|
|
12
13
|
import latestVersion from "latest-version";
|
|
13
14
|
import semver from "semver";
|
|
14
|
-
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
15
15
|
import windowStateKeeper from "electron-window-state";
|
|
16
16
|
import contextMenu from "electron-context-menu";
|
|
17
17
|
import { FindOverlay } from "electron-find-overlay";
|
|
@@ -680,21 +680,16 @@ function extractText(content) {
|
|
|
680
680
|
if (!Array.isArray(content)) return "";
|
|
681
681
|
return content.map((b) => typeof b === "string" ? b : b?.type === "text" ? b.text : "").filter(Boolean).join("\n");
|
|
682
682
|
}
|
|
683
|
-
function shortProject(dir, depth
|
|
683
|
+
function shortProject(dir, depth) {
|
|
684
684
|
if (!dir) return "(no project)";
|
|
685
685
|
const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
686
686
|
return parts.slice(-depth).join("/") || dir;
|
|
687
687
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const m = dir?.replace(/\\/g, "/").match(TEMP_ROOT);
|
|
691
|
-
return m ? { project: dir.slice(0, m[1].length), tag: m[2] } : null;
|
|
692
|
-
}
|
|
693
|
-
function suffixLabels(dirs, depth = 2) {
|
|
694
|
-
const labels = new Map([...new Set(dirs)].map((dir) => [dir, shortProject(dir, depth)]));
|
|
688
|
+
function suffixLabels(dirs) {
|
|
689
|
+
const labels = new Map([...new Set(dirs)].map((dir) => [dir, shortProject(dir, 1)]));
|
|
695
690
|
const counts = /* @__PURE__ */ new Map();
|
|
696
691
|
for (const label of labels.values()) counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
697
|
-
for (const [dir, label] of labels) if (counts.get(label) > 1) labels.set(dir, shortProject(dir,
|
|
692
|
+
for (const [dir, label] of labels) if (counts.get(label) > 1) labels.set(dir, shortProject(dir, 2));
|
|
698
693
|
return labels;
|
|
699
694
|
}
|
|
700
695
|
function classifySource(meta) {
|
|
@@ -715,10 +710,6 @@ function freshMeta({ sessionId, parentSessionId, filePath, parentFilePath, fileS
|
|
|
715
710
|
continuesTo: null,
|
|
716
711
|
entrypoint: null,
|
|
717
712
|
project: null,
|
|
718
|
-
worktree: null,
|
|
719
|
-
worktreePath: null,
|
|
720
|
-
tempPath: null,
|
|
721
|
-
tempTag: null,
|
|
722
713
|
gitBranch: null,
|
|
723
714
|
version: null,
|
|
724
715
|
model: null,
|
|
@@ -937,19 +928,6 @@ class SessionParser {
|
|
|
937
928
|
if (meta.startedAt == null) meta.startedAt = mtimeFallback;
|
|
938
929
|
if (meta.lastActivityAt == null) meta.lastActivityAt = mtimeFallback;
|
|
939
930
|
meta.source = classifySource(meta);
|
|
940
|
-
const wt = meta.project && meta.project.replace(/\\/g, "/").match(/\/\.claude\/worktrees\/([^/]+)/);
|
|
941
|
-
if (wt) {
|
|
942
|
-
meta.worktree = wt[1];
|
|
943
|
-
meta.worktreePath = meta.project.slice(0, wt.index + wt[0].length);
|
|
944
|
-
meta.project = meta.project.slice(0, wt.index);
|
|
945
|
-
}
|
|
946
|
-
const tmp = tempProject(meta.project);
|
|
947
|
-
if (tmp) {
|
|
948
|
-
meta.tempPath = meta.project;
|
|
949
|
-
meta.tempTag = tmp.tag;
|
|
950
|
-
meta.project = tmp.project;
|
|
951
|
-
}
|
|
952
|
-
meta.projectShort = shortProject(meta.project);
|
|
953
931
|
meta.activeMs = meta.activityPeriods.reduce((sum, p) => sum + Math.max(0, p.end - p.start), 0);
|
|
954
932
|
const t = meta.tokens;
|
|
955
933
|
meta.totalTokens = t.input + t.output + t.cacheRead + t.cacheCreation;
|
|
@@ -1235,6 +1213,52 @@ class MetaCache {
|
|
|
1235
1213
|
return this.periodMetas.delete(filePath);
|
|
1236
1214
|
}
|
|
1237
1215
|
}
|
|
1216
|
+
const WORKTREE = /\/\.claude\/worktrees\/([^/]+)/;
|
|
1217
|
+
const TEMP_ROOT = /^(.*?\/AppData\/Local\/Temp|\/(?:private\/)?(?:tmp|var\/folders\/[^/]+\/[^/]+\/T))\/(.+)/i;
|
|
1218
|
+
const WINDOWS = process.platform === "win32";
|
|
1219
|
+
const LOCAL_DIR = WINDOWS ? /^[a-z]:[\\/]/i : /^\//;
|
|
1220
|
+
class ProjectResolver {
|
|
1221
|
+
#repoRoots = /* @__PURE__ */ new Map();
|
|
1222
|
+
// `subdir` is where in the project the session ran: a repo or worktree subfolder, or a temp run dir
|
|
1223
|
+
async resolve(cwd) {
|
|
1224
|
+
const out = { project: cwd, subdir: null, worktree: null, worktreePath: null, tempPath: null };
|
|
1225
|
+
const wt = cwd?.replace(/\\/g, "/").match(WORKTREE);
|
|
1226
|
+
if (wt) {
|
|
1227
|
+
out.worktree = wt[1];
|
|
1228
|
+
out.worktreePath = cwd.slice(0, wt.index + wt[0].length);
|
|
1229
|
+
out.subdir = cwd.slice(out.worktreePath.length + 1).replace(/\\/g, "/") || null;
|
|
1230
|
+
out.project = cwd.slice(0, wt.index);
|
|
1231
|
+
}
|
|
1232
|
+
const tmp = out.project?.replace(/\\/g, "/").match(TEMP_ROOT);
|
|
1233
|
+
if (tmp) {
|
|
1234
|
+
out.tempPath = out.project;
|
|
1235
|
+
out.subdir = tmp[2];
|
|
1236
|
+
out.project = out.project.slice(0, tmp[1].length);
|
|
1237
|
+
return out;
|
|
1238
|
+
}
|
|
1239
|
+
const root = await this.repoRoot(out.project);
|
|
1240
|
+
if (root && root !== out.project) {
|
|
1241
|
+
out.subdir = [path.relative(root, out.project).replaceAll("\\", "/"), out.subdir].filter(Boolean).join("/");
|
|
1242
|
+
out.project = root;
|
|
1243
|
+
}
|
|
1244
|
+
return out;
|
|
1245
|
+
}
|
|
1246
|
+
// The nearest ancestor (or dir itself) holding a .git — a dir, or a file in worktrees/submodules — else null
|
|
1247
|
+
repoRoot(dir) {
|
|
1248
|
+
if (!LOCAL_DIR.test(dir ?? "")) return Promise.resolve(null);
|
|
1249
|
+
if (!this.#repoRoots.has(dir)) this.#repoRoots.set(dir, this.#findRepoRoot(dir));
|
|
1250
|
+
return this.#repoRoots.get(dir);
|
|
1251
|
+
}
|
|
1252
|
+
async #findRepoRoot(dir) {
|
|
1253
|
+
const home = os.homedir();
|
|
1254
|
+
const isHome = WINDOWS ? (d) => d.toLowerCase() === home.toLowerCase() : (d) => d === home;
|
|
1255
|
+
for (let d = dir; ; d = path.dirname(d)) {
|
|
1256
|
+
if (isHome(d)) return null;
|
|
1257
|
+
if (await fsp.stat(path.join(d, ".git")).catch(() => null)) return d;
|
|
1258
|
+
if (path.dirname(d) === d) return null;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1238
1262
|
const seed = {
|
|
1239
1263
|
"claude-opus-5": { "input": 5, "output": 25, "cacheRead": 0.5, "cacheWrite": 6.25, "cacheWrite1h": 10, "fastModeMplr": 2 },
|
|
1240
1264
|
"claude-fable-5-1": { "input": 10, "output": 50, "cacheRead": 0.25, "cacheWrite": 12.5, "cacheWrite1h": 20 },
|
|
@@ -1381,6 +1405,7 @@ class SessionsService extends EventEmitter {
|
|
|
1381
1405
|
this.pricing = new Pricing();
|
|
1382
1406
|
this.throttleMs = throttleMs;
|
|
1383
1407
|
this.metaCache = new MetaCache();
|
|
1408
|
+
this.projects = new ProjectResolver();
|
|
1384
1409
|
this.activeDay = null;
|
|
1385
1410
|
this.scanAbort = null;
|
|
1386
1411
|
this.updateTimer = null;
|
|
@@ -1453,7 +1478,10 @@ class SessionsService extends EventEmitter {
|
|
|
1453
1478
|
start() {
|
|
1454
1479
|
this.pricing.on("update", () => this._reprice());
|
|
1455
1480
|
this.scanner.watch({
|
|
1456
|
-
onChange: (p, stat) =>
|
|
1481
|
+
onChange: (p, stat) => {
|
|
1482
|
+
this.emit("activity");
|
|
1483
|
+
this._refresh(p, stat);
|
|
1484
|
+
},
|
|
1457
1485
|
onUnlink: (p) => this._evict(p)
|
|
1458
1486
|
});
|
|
1459
1487
|
return this;
|
|
@@ -1484,7 +1512,8 @@ class SessionsService extends EventEmitter {
|
|
|
1484
1512
|
pricing: this.pricing
|
|
1485
1513
|
});
|
|
1486
1514
|
await reader.stream((obj) => parser.feed(obj));
|
|
1487
|
-
|
|
1515
|
+
const meta = parser.finalize(stat.mtimeMs);
|
|
1516
|
+
return Object.assign(meta, await this.projects.resolve(meta.project));
|
|
1488
1517
|
}
|
|
1489
1518
|
// Resume/fork copies prior message.ids verbatim. Walk earliest-first; if a
|
|
1490
1519
|
// session's ids overlap one already counted, rescan it with the overlap
|
|
@@ -1506,7 +1535,7 @@ class SessionsService extends EventEmitter {
|
|
|
1506
1535
|
return stripInternal(rescanned ? { ...m, ...rescanned } : m);
|
|
1507
1536
|
}));
|
|
1508
1537
|
const labels = suffixLabels(sessions.map((m) => m.project));
|
|
1509
|
-
for (const m of sessions) m.projectShort = labels.get(m.project);
|
|
1538
|
+
for (const m of sessions) m.projectShort = m.tempPath ? "Temp" : labels.get(m.project);
|
|
1510
1539
|
return sessions;
|
|
1511
1540
|
}
|
|
1512
1541
|
_dedupedDay(day) {
|
|
@@ -1571,6 +1600,53 @@ class WorkHours {
|
|
|
1571
1600
|
return data;
|
|
1572
1601
|
}
|
|
1573
1602
|
}
|
|
1603
|
+
const VIEWABLE_EXT = /* @__PURE__ */ new Set([".md", ".txt", ".json", ".jsonl", ".log", ".csv", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".pdf"]);
|
|
1604
|
+
async function openLinkSafely(href, baseFile) {
|
|
1605
|
+
if (/^(https?:|mailto:)/i.test(href)) return shell.openExternal(href);
|
|
1606
|
+
if (!baseFile) return;
|
|
1607
|
+
let decoded;
|
|
1608
|
+
try {
|
|
1609
|
+
decoded = decodeURIComponent(href);
|
|
1610
|
+
} catch {
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
const baseDir = path.dirname(path.resolve(baseFile));
|
|
1614
|
+
const abs = path.resolve(baseDir, decoded);
|
|
1615
|
+
const rel = path.relative(baseDir, abs);
|
|
1616
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) return;
|
|
1617
|
+
if (!VIEWABLE_EXT.has(path.extname(abs).toLowerCase())) return shell.showItemInFolder(abs);
|
|
1618
|
+
const err = await shell.openPath(abs);
|
|
1619
|
+
if (err) shell.showItemInFolder(abs);
|
|
1620
|
+
return err;
|
|
1621
|
+
}
|
|
1622
|
+
const DEV_ORIGIN = process.env.ELECTRON_RENDERER_URL && new URL(process.env.ELECTRON_RENDERER_URL).origin;
|
|
1623
|
+
const APP_PAGES = pathToFileURL(path.join(import.meta.dirname, "../renderer/")).href;
|
|
1624
|
+
function lockNavigation(contents) {
|
|
1625
|
+
const toBrowser = (url) => /^(https?|mailto):/i.test(url) && shell.openExternal(url);
|
|
1626
|
+
const block = (e, url) => {
|
|
1627
|
+
const { href, origin } = new URL(url);
|
|
1628
|
+
if (href.startsWith(APP_PAGES) || origin === DEV_ORIGIN) return;
|
|
1629
|
+
e.preventDefault();
|
|
1630
|
+
toBrowser(url);
|
|
1631
|
+
};
|
|
1632
|
+
contents.setWindowOpenHandler(({ url }) => {
|
|
1633
|
+
toBrowser(url);
|
|
1634
|
+
return { action: "deny" };
|
|
1635
|
+
});
|
|
1636
|
+
contents.on("will-navigate", block);
|
|
1637
|
+
contents.on("will-redirect", block);
|
|
1638
|
+
}
|
|
1639
|
+
async function withTimeout(promise, ms, cleanup) {
|
|
1640
|
+
const { promise: timeout, reject } = Promise.withResolvers();
|
|
1641
|
+
const timer = setTimeout(reject, ms, new Error(`timed out after ${ms / 1e3}s`));
|
|
1642
|
+
try {
|
|
1643
|
+
return await Promise.race([promise, timeout]);
|
|
1644
|
+
} finally {
|
|
1645
|
+
clearTimeout(timer);
|
|
1646
|
+
cleanup?.();
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
const USAGE_TIMEOUT = 3e4;
|
|
1574
1650
|
class AgentRunner {
|
|
1575
1651
|
async run(text, sender) {
|
|
1576
1652
|
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
@@ -1597,6 +1673,14 @@ class AgentRunner {
|
|
|
1597
1673
|
}
|
|
1598
1674
|
return { code: 0 };
|
|
1599
1675
|
}
|
|
1676
|
+
// The structured /usage data, from a session that's closed whether it answers, fails or hangs
|
|
1677
|
+
async usage() {
|
|
1678
|
+
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
1679
|
+
const q = query({ prompt: (async function* () {
|
|
1680
|
+
})(), options: { tools: [], settingSources: [] } });
|
|
1681
|
+
const usage = q.usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET({ skipBehaviors: true });
|
|
1682
|
+
return withTimeout(usage, USAGE_TIMEOUT, () => q.close());
|
|
1683
|
+
}
|
|
1600
1684
|
}
|
|
1601
1685
|
const mtimeOf = (path2) => fs.statSync(path2, { throwIfNoEntry: false })?.mtimeMs;
|
|
1602
1686
|
class ClaudeSettings {
|
|
@@ -2000,41 +2084,65 @@ class AutoUpdate {
|
|
|
2000
2084
|
return semver.gt(latest, current) ? { current, latest } : null;
|
|
2001
2085
|
}
|
|
2002
2086
|
}
|
|
2003
|
-
const
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2087
|
+
const EVERY = 6e4;
|
|
2088
|
+
const toWindow = (w) => w?.utilization == null ? null : {
|
|
2089
|
+
utilization: w.utilization,
|
|
2090
|
+
// 0..100
|
|
2091
|
+
resetsAt: w.resets_at ? Math.round(Date.parse(w.resets_at) / 6e4) * 6e4 : null
|
|
2092
|
+
// the server sends 16:29:59.72 for 16:30
|
|
2093
|
+
};
|
|
2094
|
+
const toExtra = (e) => e && {
|
|
2095
|
+
on: e.is_enabled,
|
|
2096
|
+
reason: !e.is_enabled && !e.user_disabled ? e.disabled_reason : null,
|
|
2097
|
+
// e.g. 'out_of_credits'
|
|
2098
|
+
used: e.used_credits,
|
|
2099
|
+
limit: e.monthly_limit,
|
|
2100
|
+
currency: e.currency,
|
|
2101
|
+
decimals: e.decimal_places ?? 2
|
|
2102
|
+
};
|
|
2103
|
+
class RateLimits {
|
|
2104
|
+
#agentRunner;
|
|
2105
|
+
#limits = null;
|
|
2106
|
+
// the latest fetch's promise, shared by concurrent callers
|
|
2107
|
+
#fetchedAt = 0;
|
|
2108
|
+
#active = false;
|
|
2109
|
+
// a transcript changed since the last fetch
|
|
2110
|
+
constructor(agentRunner) {
|
|
2111
|
+
this.#agentRunner = agentRunner;
|
|
2112
|
+
}
|
|
2113
|
+
// A transcript changed
|
|
2114
|
+
markActive() {
|
|
2115
|
+
this.#active = true;
|
|
2116
|
+
}
|
|
2117
|
+
// Refetched once over a minute old; with `ifActive`, only if Claude Code wrote a transcript since
|
|
2118
|
+
get({ ifActive = false } = {}) {
|
|
2119
|
+
if (Date.now() - this.#fetchedAt >= EVERY && (this.#active || !ifActive)) {
|
|
2120
|
+
this.#fetchedAt = Date.now();
|
|
2121
|
+
this.#active = false;
|
|
2122
|
+
this.#limits = this.#fetch();
|
|
2123
|
+
}
|
|
2124
|
+
return this.#limits;
|
|
2125
|
+
}
|
|
2126
|
+
async #fetch() {
|
|
2127
|
+
try {
|
|
2128
|
+
const { subscription_type: plan, rate_limits: rl } = await this.#agentRunner.usage();
|
|
2129
|
+
return rl && {
|
|
2130
|
+
at: Date.now(),
|
|
2131
|
+
plan,
|
|
2132
|
+
// 'pro' | 'max' | 'team' | 'enterprise'
|
|
2133
|
+
fiveHour: toWindow(rl.five_hour),
|
|
2134
|
+
sevenDay: toWindow(rl.seven_day),
|
|
2135
|
+
models: (rl.model_scoped ?? []).filter((m) => m.utilization != null).map((m) => ({ name: m.display_name, ...toWindow(m) })),
|
|
2136
|
+
// per-model weekly caps
|
|
2137
|
+
sources: (rl.seven_day_breakdown?.rows ?? []).filter((r) => r.percent > 0).map((r) => ({ name: r.display_name, pct: r.percent })),
|
|
2138
|
+
// undocumented: Claude Code / Chats / Cowork shares of the week
|
|
2139
|
+
extra: toExtra(rl.extra_usage)
|
|
2140
|
+
};
|
|
2141
|
+
} catch (err) {
|
|
2142
|
+
console.warn("[rate-limits]", err.message);
|
|
2143
|
+
return null;
|
|
2144
|
+
}
|
|
2012
2145
|
}
|
|
2013
|
-
const baseDir = path.dirname(path.resolve(baseFile));
|
|
2014
|
-
const abs = path.resolve(baseDir, decoded);
|
|
2015
|
-
const rel = path.relative(baseDir, abs);
|
|
2016
|
-
if (rel.startsWith("..") || path.isAbsolute(rel)) return;
|
|
2017
|
-
if (!VIEWABLE_EXT.has(path.extname(abs).toLowerCase())) return shell.showItemInFolder(abs);
|
|
2018
|
-
const err = await shell.openPath(abs);
|
|
2019
|
-
if (err) shell.showItemInFolder(abs);
|
|
2020
|
-
return err;
|
|
2021
|
-
}
|
|
2022
|
-
const DEV_ORIGIN = process.env.ELECTRON_RENDERER_URL && new URL(process.env.ELECTRON_RENDERER_URL).origin;
|
|
2023
|
-
const APP_PAGES = pathToFileURL(path.join(import.meta.dirname, "../renderer/")).href;
|
|
2024
|
-
function lockNavigation(contents) {
|
|
2025
|
-
const toBrowser = (url) => /^(https?|mailto):/i.test(url) && shell.openExternal(url);
|
|
2026
|
-
const block = (e, url) => {
|
|
2027
|
-
const { href, origin } = new URL(url);
|
|
2028
|
-
if (href.startsWith(APP_PAGES) || origin === DEV_ORIGIN) return;
|
|
2029
|
-
e.preventDefault();
|
|
2030
|
-
toBrowser(url);
|
|
2031
|
-
};
|
|
2032
|
-
contents.setWindowOpenHandler(({ url }) => {
|
|
2033
|
-
toBrowser(url);
|
|
2034
|
-
return { action: "deny" };
|
|
2035
|
-
});
|
|
2036
|
-
contents.on("will-navigate", block);
|
|
2037
|
-
contents.on("will-redirect", block);
|
|
2038
2146
|
}
|
|
2039
2147
|
const __dirname$1 = path.dirname(fileURLToPath(import.meta.url));
|
|
2040
2148
|
const DEV_URL = process.env.ELECTRON_RENDERER_URL;
|
|
@@ -2072,6 +2180,7 @@ class MainWindow {
|
|
|
2072
2180
|
this.findBar = new FindOverlay(this.win);
|
|
2073
2181
|
this.findBar.on("show", () => this.send("find:active", true));
|
|
2074
2182
|
this.findBar.on("hide", () => this.send("find:active", false));
|
|
2183
|
+
this.win.on("closed", () => this.findBar = null);
|
|
2075
2184
|
return this.win;
|
|
2076
2185
|
}
|
|
2077
2186
|
send(channel, payload) {
|
|
@@ -2092,13 +2201,15 @@ class Application {
|
|
|
2092
2201
|
this.workHours = new WorkHours();
|
|
2093
2202
|
this.sessionsService = new SessionsService();
|
|
2094
2203
|
this.autoUpdate = new AutoUpdate();
|
|
2204
|
+
this.rateLimits = new RateLimits(this.agentRunner);
|
|
2095
2205
|
this.switchers = new Switchers({ restart: () => this.restart() });
|
|
2096
2206
|
}
|
|
2097
2207
|
start() {
|
|
2098
|
-
const { deepLink, win, agentRunner, workHours, sessionsService, autoUpdate, switchers } = this;
|
|
2208
|
+
const { deepLink, win, agentRunner, workHours, sessionsService, autoUpdate, rateLimits, switchers } = this;
|
|
2099
2209
|
Menu.setApplicationMenu(this.#buildMenu());
|
|
2100
2210
|
sessionsService.on("update", (update) => win.send("sessions:update", update));
|
|
2101
2211
|
sessionsService.on("progress", (p) => win.send("sessions:scan-progress", p));
|
|
2212
|
+
sessionsService.on("activity", () => rateLimits.markActive());
|
|
2102
2213
|
deepLink?.on("open", (target) => {
|
|
2103
2214
|
win.focus();
|
|
2104
2215
|
win.send("deeplink:open-session", target);
|
|
@@ -2114,6 +2225,7 @@ class Application {
|
|
|
2114
2225
|
ipcMain.handle("work-hours:set", (_e, data) => workHours.write(data));
|
|
2115
2226
|
ipcMain.handle("auto-update:check", () => autoUpdate.check());
|
|
2116
2227
|
ipcMain.handle("auto-update:install", () => autoUpdate.install());
|
|
2228
|
+
ipcMain.handle("rate-limits:get", (_e, opts) => rateLimits.get(opts));
|
|
2117
2229
|
ipcMain.handle("agent:run", (e, text) => agentRunner.run(text, e.sender));
|
|
2118
2230
|
ipcMain.handle("shell:open-link", (_e, href, baseFile) => openLinkSafely(href, baseFile));
|
|
2119
2231
|
ipcMain.handle("deeplink:take-pending", () => deepLink?.takePending() ?? null);
|
|
@@ -2170,6 +2282,8 @@ class Application {
|
|
|
2170
2282
|
{ role: "paste" },
|
|
2171
2283
|
{ role: "selectAll" },
|
|
2172
2284
|
{ label: "Find…", accelerator: "CmdOrCtrl+F", click: () => win.findBar?.show() },
|
|
2285
|
+
{ label: "Find Next", accelerator: "CmdOrCtrl+G", click: () => win.findBar?.next() },
|
|
2286
|
+
{ label: "Find Previous", accelerator: "CmdOrCtrl+Shift+G", click: () => win.findBar?.prev() },
|
|
2173
2287
|
{ label: "Deselect", accelerator: "Escape", click: (_i, w) => win.findBar?.visible ? win.findBar.hide() : w?.webContents.unselect() }
|
|
2174
2288
|
]
|
|
2175
2289
|
},
|
package/out/preload/preload.cjs
CHANGED
|
@@ -24,6 +24,7 @@ electron.contextBridge.exposeInMainWorld("api", {
|
|
|
24
24
|
onAgentOutput: subscribe("agent:output"),
|
|
25
25
|
checkUpdate: () => electron.ipcRenderer.invoke("auto-update:check"),
|
|
26
26
|
installUpdate: () => electron.ipcRenderer.invoke("auto-update:install"),
|
|
27
|
+
getRateLimits: (opts) => electron.ipcRenderer.invoke("rate-limits:get", opts),
|
|
27
28
|
openLink: (href, baseFile) => electron.ipcRenderer.invoke("shell:open-link", href, baseFile),
|
|
28
29
|
onFindActive: subscribe("find:active"),
|
|
29
30
|
findClose: () => electron.ipcRenderer.send("find:close"),
|