claude-discover 1.9.5 → 1.10.1
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/main.js +185 -54
- package/out/renderer/assets/{index-DZ1Gj6hz.css → index-DnYN00T3.css} +6 -0
- package/out/renderer/assets/{index-CX9ZlZGo.js → index-FM6CSRHb.js} +19 -11
- package/out/renderer/assets/{vendors-JM3FOKMR.js → vendors-D2DMOduT.js} +10 -9
- package/out/renderer/index.html +3 -3
- package/package.json +77 -77
package/out/main/main.js
CHANGED
|
@@ -6,7 +6,6 @@ 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 chokidar from "chokidar";
|
|
10
9
|
import which from "which";
|
|
11
10
|
import { execFile } from "node:child_process";
|
|
12
11
|
import { promisify } from "node:util";
|
|
@@ -52,6 +51,7 @@ class DeepLink extends EventEmitter {
|
|
|
52
51
|
});
|
|
53
52
|
this.#register();
|
|
54
53
|
this.#pending = findTarget(process.argv);
|
|
54
|
+
return this;
|
|
55
55
|
}
|
|
56
56
|
open(target) {
|
|
57
57
|
if (target) this.emit("open", target);
|
|
@@ -564,10 +564,10 @@ class RequestParser {
|
|
|
564
564
|
for (const t of fresh) this.seenTools.add(t.name);
|
|
565
565
|
const schema = (t) => t.input_schema ? "```json\n" + JSON.stringify(t.input_schema, null, 2) + "\n```" : "";
|
|
566
566
|
const strip = (file_path, pick) => {
|
|
567
|
-
const list = fresh.filter((t) => pick(t.name));
|
|
567
|
+
const list = fresh.filter((t) => pick(t.name)).map((t) => ({ name: t.name, content: [t.description || "", schema(t)].filter(Boolean).join("\n\n") }));
|
|
568
568
|
if (!list.length) return null;
|
|
569
|
-
const content = list.map((t) => [`## ${t.name}`, t.
|
|
570
|
-
return toStrip(rec, "tools", file_path, `${list.length} tool${list.length === 1 ? "" : "s"}`, content);
|
|
569
|
+
const content = list.map((t) => [`## ${t.name}`, t.content].filter(Boolean).join("\n\n")).join("\n\n");
|
|
570
|
+
return { ...toStrip(rec, "tools", file_path, `${list.length} tool${list.length === 1 ? "" : "s"}`, content), tools: list };
|
|
571
571
|
};
|
|
572
572
|
return [strip("System Tools", (n) => !isMcp(n)), strip("MCP Tools", isMcp)].filter(Boolean);
|
|
573
573
|
}
|
|
@@ -710,6 +710,7 @@ function freshMeta({ sessionId, parentSessionId, filePath, parentFilePath, fileS
|
|
|
710
710
|
aiTitle: null,
|
|
711
711
|
customTitle: null,
|
|
712
712
|
agentName: null,
|
|
713
|
+
tag: null,
|
|
713
714
|
firstUserPrompt: null,
|
|
714
715
|
firstUserCommand: null,
|
|
715
716
|
forkedFrom: null,
|
|
@@ -747,6 +748,7 @@ class SessionParser {
|
|
|
747
748
|
if (t === "ai-title" && obj.aiTitle) meta.aiTitle = obj.aiTitle;
|
|
748
749
|
if (t === "custom-title" && obj.customTitle) meta.customTitle = obj.customTitle;
|
|
749
750
|
if (t === "agent-name" && obj.agentName) meta.agentName = obj.agentName;
|
|
751
|
+
if (t === "tag") meta.tag = obj.tag || null;
|
|
750
752
|
if (t === "queue-operation" && obj.content?.includes("<scheduled-task")) meta.hasScheduledTask = true;
|
|
751
753
|
if (t === "started" && obj.key && obj.agentId) meta.workflowAgents += 1;
|
|
752
754
|
if (obj.cwd && !meta.project) meta.project = obj.cwd;
|
|
@@ -943,25 +945,130 @@ class StatCache {
|
|
|
943
945
|
onProgress?.({ done: 1, total: 1, scanning: false });
|
|
944
946
|
}
|
|
945
947
|
}
|
|
948
|
+
async function listFiles(dir, filter) {
|
|
949
|
+
const entries = await fsp.readdir(dir, { recursive: true, withFileTypes: true });
|
|
950
|
+
return entries.filter((e) => e.isFile() && filter(e.name)).map((e) => path.join(e.parentPath, e.name));
|
|
951
|
+
}
|
|
952
|
+
class FileWatcher {
|
|
953
|
+
#root;
|
|
954
|
+
#opts;
|
|
955
|
+
#watcher = null;
|
|
956
|
+
#closed = false;
|
|
957
|
+
constructor(root, opts) {
|
|
958
|
+
this.#root = root;
|
|
959
|
+
this.#opts = opts;
|
|
960
|
+
try {
|
|
961
|
+
this.#watcher = new EventFileWatcher(root, { ...opts, onFail: (err) => this.#fallBack(err) });
|
|
962
|
+
} catch (err) {
|
|
963
|
+
this.#fallBack(err);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
get mode() {
|
|
967
|
+
return this.#watcher instanceof IntervalFileWatcher ? "interval" : "event";
|
|
968
|
+
}
|
|
969
|
+
// Its first diff against the baseline also catches whatever changed while events were down
|
|
970
|
+
#fallBack(err) {
|
|
971
|
+
if (this.#closed) return;
|
|
972
|
+
console.warn("[watch] checking on an interval instead:", err.message);
|
|
973
|
+
this.#watcher = new IntervalFileWatcher(this.#root, this.#opts);
|
|
974
|
+
}
|
|
975
|
+
close() {
|
|
976
|
+
this.#closed = true;
|
|
977
|
+
this.#watcher?.close();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
class EventFileWatcher {
|
|
981
|
+
#opts;
|
|
982
|
+
#handle = null;
|
|
983
|
+
#timers = /* @__PURE__ */ new Map();
|
|
984
|
+
// filePath -> pending settle timer
|
|
985
|
+
#closed = false;
|
|
986
|
+
// Throws if the OS refuses the watch, e.g. on \\wsl.localhost\...
|
|
987
|
+
constructor(root, opts) {
|
|
988
|
+
this.#opts = { settleMs: 100, ...opts };
|
|
989
|
+
this.#handle = fs.watch(root, { recursive: true }, (_type, name) => {
|
|
990
|
+
if (!name) return this.#opts.onDrop();
|
|
991
|
+
if (this.#opts.filter(name)) this.#settle(path.join(root, name));
|
|
992
|
+
});
|
|
993
|
+
this.#handle.on("error", (err) => {
|
|
994
|
+
this.close();
|
|
995
|
+
this.#opts.onFail(err);
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
// Events carry no stat or kind, so stat each path once its writes settle
|
|
999
|
+
#settle(filePath) {
|
|
1000
|
+
const { onChange, onUnlink, settleMs } = this.#opts;
|
|
1001
|
+
clearTimeout(this.#timers.get(filePath));
|
|
1002
|
+
this.#timers.set(filePath, setTimeout(async () => {
|
|
1003
|
+
this.#timers.delete(filePath);
|
|
1004
|
+
const stat = await fsp.stat(filePath).catch(() => null);
|
|
1005
|
+
if (this.#closed) return;
|
|
1006
|
+
if (!stat) onUnlink(filePath);
|
|
1007
|
+
else if (stat.isFile()) onChange(filePath, stat);
|
|
1008
|
+
}, settleMs));
|
|
1009
|
+
}
|
|
1010
|
+
close() {
|
|
1011
|
+
this.#closed = true;
|
|
1012
|
+
for (const timer of this.#timers.values()) {
|
|
1013
|
+
clearTimeout(timer);
|
|
1014
|
+
}
|
|
1015
|
+
this.#timers.clear();
|
|
1016
|
+
this.#handle?.close();
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
class IntervalFileWatcher {
|
|
1020
|
+
#root;
|
|
1021
|
+
#opts;
|
|
1022
|
+
#timer = null;
|
|
1023
|
+
#checks = 0;
|
|
1024
|
+
#closed = false;
|
|
1025
|
+
// Stats on a share are the bulk of a check, so each one stats only new files and those written within hotMs;
|
|
1026
|
+
// every sweepEvery-th check stats them all, catching an idle transcript that grows again (e.g. a resumed session)
|
|
1027
|
+
constructor(root, opts) {
|
|
1028
|
+
this.#root = root;
|
|
1029
|
+
this.#opts = { intervalMs: 2e3, hotMs: 10 * 6e4, sweepEvery: 30, ...opts };
|
|
1030
|
+
this.#schedule();
|
|
1031
|
+
}
|
|
1032
|
+
// Timed from the end of the last check, so a slow share can't pile them up
|
|
1033
|
+
#schedule() {
|
|
1034
|
+
this.#timer = setTimeout(() => this.#check().finally(() => this.#closed || this.#schedule()), this.#opts.intervalMs);
|
|
1035
|
+
}
|
|
1036
|
+
async #check() {
|
|
1037
|
+
const { filter, onChange, onUnlink, baseline } = this.#opts;
|
|
1038
|
+
const known = baseline();
|
|
1039
|
+
if (!known) return;
|
|
1040
|
+
const files = await listFiles(this.#root, filter).catch(() => null);
|
|
1041
|
+
if (!files) return;
|
|
1042
|
+
const sweep = ++this.#checks % this.#opts.sweepEvery === 0;
|
|
1043
|
+
const now = Date.now();
|
|
1044
|
+
await Promise.all(files.map(async (fp) => {
|
|
1045
|
+
const old = known.get(fp);
|
|
1046
|
+
if (!sweep && old && now - old.mtimeMs >= this.#opts.hotMs) return;
|
|
1047
|
+
const stat = await fsp.stat(fp).catch(() => null);
|
|
1048
|
+
if (stat && !this.#closed && (!old || old.size !== stat.size || old.mtimeMs !== stat.mtimeMs)) onChange(fp, stat);
|
|
1049
|
+
}));
|
|
1050
|
+
if (this.#closed) return;
|
|
1051
|
+
const listed = new Set(files);
|
|
1052
|
+
for (const fp of known.keys()) {
|
|
1053
|
+
if (!listed.has(fp)) onUnlink(fp);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
close() {
|
|
1057
|
+
this.#closed = true;
|
|
1058
|
+
clearTimeout(this.#timer);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
946
1061
|
const isJsonl = (p) => p.endsWith(".jsonl");
|
|
947
1062
|
class SessionsScanner {
|
|
948
1063
|
constructor({ root = CLAUDE_PROJECTS_DIR } = {}) {
|
|
949
1064
|
this.root = root;
|
|
950
1065
|
this.watcher = null;
|
|
951
|
-
this.watchWanted = false;
|
|
952
|
-
this.walkDone = Promise.withResolvers();
|
|
953
1066
|
this.statCache = new StatCache();
|
|
954
1067
|
}
|
|
955
1068
|
async _listDirs(p) {
|
|
956
1069
|
const entries = await fsp.readdir(p, { withFileTypes: true }).catch(() => []);
|
|
957
1070
|
return entries.filter((e) => e.isDirectory()).map((e) => path.join(e.parentPath, e.name));
|
|
958
1071
|
}
|
|
959
|
-
// Every .jsonl under `dir`, at any depth — subagent transcripts nest arbitrarily deep. Subtrees
|
|
960
|
-
// can't be pruned by directory mtime: on Windows, appending to a transcript doesn't bump its parent dir.
|
|
961
|
-
async _listJsonl(dir) {
|
|
962
|
-
const entries = await fsp.readdir(dir, { recursive: true, withFileTypes: true }).catch(() => []);
|
|
963
|
-
return entries.filter((e) => e.isFile() && isJsonl(e.name)).map((e) => path.join(e.parentPath, e.name));
|
|
964
|
-
}
|
|
965
1072
|
// Append-only transcripts span [birthtime, mtime], so files outside the period are skipped
|
|
966
1073
|
// Note birthtime is trusted only when well older than mtime, since copies/restores (git checkout, scp, zip) get a fresh birthtime over old content.
|
|
967
1074
|
_inPeriod(stat, day) {
|
|
@@ -973,14 +1080,10 @@ class SessionsScanner {
|
|
|
973
1080
|
// `signal` (a superseded scan) stops everything; once complete with the watcher live, the
|
|
974
1081
|
// StatCache mirrors disk and serves later scans in memory.
|
|
975
1082
|
async scan(day, opts = {}) {
|
|
976
|
-
if (this.statCache.complete
|
|
1083
|
+
if (this.statCache.complete) {
|
|
977
1084
|
return this.statCache.scan((stat) => this._inPeriod(stat, day), opts);
|
|
978
1085
|
}
|
|
979
|
-
|
|
980
|
-
await this._walk(day, opts);
|
|
981
|
-
} finally {
|
|
982
|
-
this.walkDone.resolve();
|
|
983
|
-
}
|
|
1086
|
+
return this._walk(day, opts);
|
|
984
1087
|
}
|
|
985
1088
|
async _walk(day, { onFile, onBatchDone, onProgress, signal } = {}) {
|
|
986
1089
|
const projects = await this._listDirs(this.root);
|
|
@@ -989,7 +1092,8 @@ class SessionsScanner {
|
|
|
989
1092
|
progress();
|
|
990
1093
|
await Promise.all(projects.map(async (projDir) => {
|
|
991
1094
|
if (signal?.aborted) return;
|
|
992
|
-
const
|
|
1095
|
+
const files = await listFiles(projDir, isJsonl).catch(() => []);
|
|
1096
|
+
const results = await Promise.all(files.map(async (fp) => {
|
|
993
1097
|
const stat = signal?.aborted ? null : await fsp.stat(fp).catch(() => null);
|
|
994
1098
|
this.statCache.record(fp, stat);
|
|
995
1099
|
return stat && this._inPeriod(stat, day) && !signal?.aborted ? onFile?.(fp, stat) : null;
|
|
@@ -1004,38 +1108,28 @@ class SessionsScanner {
|
|
|
1004
1108
|
if (onBatchDone) await onBatchDone();
|
|
1005
1109
|
progress();
|
|
1006
1110
|
}
|
|
1007
|
-
// Starts
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1111
|
+
// Starts before the first walk, so that walk already completes the StatCache
|
|
1112
|
+
watch({ onChange, onUnlink }) {
|
|
1113
|
+
try {
|
|
1114
|
+
fs.mkdirSync(this.root, { recursive: true });
|
|
1115
|
+
} catch {
|
|
1116
|
+
}
|
|
1117
|
+
this.watcher = new FileWatcher(this.root, {
|
|
1118
|
+
filter: isJsonl,
|
|
1119
|
+
onChange: (p, stat) => {
|
|
1120
|
+
this.statCache.record(p, stat);
|
|
1121
|
+
onChange(p, stat);
|
|
1122
|
+
},
|
|
1123
|
+
onUnlink: (p) => {
|
|
1124
|
+
this.statCache.remove(p);
|
|
1125
|
+
onUnlink(p);
|
|
1126
|
+
},
|
|
1127
|
+
onDrop: () => this.statCache.clear(),
|
|
1128
|
+
baseline: () => this.statCache.complete ? this.statCache.stats : null
|
|
1129
|
+
// null until a full walk exists to diff against
|
|
1022
1130
|
});
|
|
1023
|
-
const changed = (p, stat) => {
|
|
1024
|
-
if (!isJsonl(p)) return;
|
|
1025
|
-
this.statCache.record(p, stat);
|
|
1026
|
-
onChange?.(p, stat);
|
|
1027
|
-
};
|
|
1028
|
-
const removed = (p) => {
|
|
1029
|
-
if (!isJsonl(p)) return;
|
|
1030
|
-
this.statCache.remove(p);
|
|
1031
|
-
onUnlink?.(p);
|
|
1032
|
-
};
|
|
1033
|
-
this.watcher.on("add", changed);
|
|
1034
|
-
this.watcher.on("change", changed);
|
|
1035
|
-
this.watcher.on("unlink", removed);
|
|
1036
1131
|
}
|
|
1037
1132
|
stop() {
|
|
1038
|
-
this.watchWanted = false;
|
|
1039
1133
|
if (this.watcher) {
|
|
1040
1134
|
this.watcher.close();
|
|
1041
1135
|
this.watcher = null;
|
|
@@ -1993,7 +2087,7 @@ class Application {
|
|
|
1993
2087
|
Menu.setApplicationMenu(this.#buildMenu());
|
|
1994
2088
|
sessionsService.on("update", (sessions) => win.send("sessions:update", sessions));
|
|
1995
2089
|
sessionsService.on("progress", (p) => win.send("sessions:scan-progress", p));
|
|
1996
|
-
deepLink
|
|
2090
|
+
deepLink?.on("open", (target) => {
|
|
1997
2091
|
win.focus();
|
|
1998
2092
|
win.send("deeplink:open-session", target);
|
|
1999
2093
|
});
|
|
@@ -2008,7 +2102,7 @@ class Application {
|
|
|
2008
2102
|
ipcMain.handle("work-hours:set", (_e, data) => workHours.write(data));
|
|
2009
2103
|
ipcMain.handle("agent:run", (e, text, systemTools, cache) => agentRunner.run(text, e.sender, systemTools, cache));
|
|
2010
2104
|
ipcMain.handle("shell:open-link", (_e, href, baseFile) => openLinkSafely(href, baseFile));
|
|
2011
|
-
ipcMain.handle("deeplink:take-pending", () => deepLink
|
|
2105
|
+
ipcMain.handle("deeplink:take-pending", () => deepLink?.takePending() ?? null);
|
|
2012
2106
|
ipcMain.on("find:query", (_e, text, options) => win.findBar?.query(text, options));
|
|
2013
2107
|
ipcMain.on("find:stop", () => win.findBar?.stop());
|
|
2014
2108
|
ipcMain.on("find:close", () => win.findBar?.hide());
|
|
@@ -2078,11 +2172,41 @@ class Application {
|
|
|
2078
2172
|
]);
|
|
2079
2173
|
}
|
|
2080
2174
|
}
|
|
2175
|
+
const APP_ID = "claude-discover";
|
|
2176
|
+
class Shortcuts {
|
|
2177
|
+
// Call before any window is created, so the window picks up the app id.
|
|
2178
|
+
activate() {
|
|
2179
|
+
if (process.platform !== "win32") return;
|
|
2180
|
+
app.setAppUserModelId(APP_ID);
|
|
2181
|
+
if (IS_EPHEMERAL || false) return;
|
|
2182
|
+
const installed = app.getAppPath().split(/[\\/]/).includes("node_modules");
|
|
2183
|
+
const name = installed ? "Claude Discover" : "Claude Discover (local)";
|
|
2184
|
+
const created = config.read().shortcuts ?? {};
|
|
2185
|
+
const install = fs.statSync(app.getAppPath()).birthtimeMs;
|
|
2186
|
+
const newInstall = created[name] !== install;
|
|
2187
|
+
for (const dir of [join(app.getPath("appData"), "Microsoft/Windows/Start Menu/Programs"), app.getPath("desktop")]) {
|
|
2188
|
+
const path2 = join(dir, `${name}.lnk`);
|
|
2189
|
+
const exists = fs.existsSync(path2);
|
|
2190
|
+
if (!exists && !newInstall) continue;
|
|
2191
|
+
const written = shell.writeShortcutLink(path2, exists ? "replace" : "create", {
|
|
2192
|
+
target: process.execPath,
|
|
2193
|
+
args: `"${app.getAppPath()}"`,
|
|
2194
|
+
description: "Browse your local Claude Code sessions",
|
|
2195
|
+
icon: process.execPath,
|
|
2196
|
+
iconIndex: 0,
|
|
2197
|
+
appUserModelId: APP_ID
|
|
2198
|
+
});
|
|
2199
|
+
if (!written) throw new Error(`cannot write ${path2}`);
|
|
2200
|
+
}
|
|
2201
|
+
if (newInstall) config.save({ shortcuts: { ...created, [name]: install } });
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2081
2204
|
const isFirstInstance = app.requestSingleInstanceLock();
|
|
2082
2205
|
if (isFirstInstance) {
|
|
2083
|
-
let application;
|
|
2084
|
-
|
|
2085
|
-
deepLink.activate();
|
|
2206
|
+
let application = null;
|
|
2207
|
+
let deepLink = null;
|
|
2208
|
+
optional("deep links", () => deepLink = new DeepLink().activate());
|
|
2209
|
+
optional("shortcuts", () => new Shortcuts().activate());
|
|
2086
2210
|
app.whenReady().then(() => {
|
|
2087
2211
|
application = new Application({ deepLink });
|
|
2088
2212
|
application.start();
|
|
@@ -2092,7 +2216,7 @@ if (isFirstInstance) {
|
|
|
2092
2216
|
if (argv.includes("--restart")) {
|
|
2093
2217
|
application?.restart();
|
|
2094
2218
|
} else if (target) {
|
|
2095
|
-
deepLink
|
|
2219
|
+
deepLink?.open(target);
|
|
2096
2220
|
} else {
|
|
2097
2221
|
application?.win.focus();
|
|
2098
2222
|
}
|
|
@@ -2100,3 +2224,10 @@ if (isFirstInstance) {
|
|
|
2100
2224
|
} else {
|
|
2101
2225
|
app.exit(0);
|
|
2102
2226
|
}
|
|
2227
|
+
function optional(label, activate) {
|
|
2228
|
+
try {
|
|
2229
|
+
activate();
|
|
2230
|
+
} catch (err) {
|
|
2231
|
+
console.warn(`[${label}] skipped:`, err.message);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
@@ -627,6 +627,8 @@
|
|
|
627
627
|
|
|
628
628
|
.worktree-tag { color: #34d399; }
|
|
629
629
|
|
|
630
|
+
.session-tag { color: #4ade80; }
|
|
631
|
+
|
|
630
632
|
.fork-tag {
|
|
631
633
|
color: var(--accent);
|
|
632
634
|
font-weight: bold;
|
|
@@ -1076,6 +1078,10 @@ button.msg-summary:hover { color: var(--text); background: var(--bg-elev-2); bor
|
|
|
1076
1078
|
.instruction-source:first-child { padding-top: 0; }
|
|
1077
1079
|
|
|
1078
1080
|
/* Full-width toggle so the estimated token count can sit right-aligned, like msg-header-right. */
|
|
1081
|
+
/* Nested tool rows cancel the aux-body's right padding so their counts line up with the strips'. */
|
|
1082
|
+
.instruction-file .instruction-file { border-left-color: var(--border); margin-right: -8px; }
|
|
1083
|
+
/* A rule under the strip's count marks it as the sum of the tool counts below. */
|
|
1084
|
+
.aux-body > .instruction-file:first-child .instruction-tokens { border-top: 1px solid var(--border); padding-top: 2px; }
|
|
1079
1085
|
.instruction-file .aux-toggle { display: flex; width: 100%; align-items: center; }
|
|
1080
1086
|
.instruction-tokens { margin-left: auto; color: var(--text-faint); }
|
|
1081
1087
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as reactExports, j as jsxRuntimeExports, f as format, s as startOfWeek, a as startOfMonth, b as startOfDay, e as endOfWeek, c as endOfMonth, d as endOfDay, g as addWeeks, h as addMonths, i as addDays, k as isSameWeek, l as isSameMonth, m as isSameDay, n as intervalToDuration, o as getDaysInMonth, p as addHours, q as encode, H as Hourglass, G as Gauge, M as MessageSquare, C as Clock, Z as Zap, D as DollarSign, R as React, t as Markdown$1, u as rehypeHighlight, v as remarkGfm, T as Terminal, J as JsonView,
|
|
1
|
+
import { r as reactExports, j as jsxRuntimeExports, f as format, s as startOfWeek, a as startOfMonth, b as startOfDay, e as endOfWeek, c as endOfMonth, d as endOfDay, g as addWeeks, h as addMonths, i as addDays, k as isSameWeek, l as isSameMonth, m as isSameDay, n as intervalToDuration, o as getDaysInMonth, p as addHours, q as encode, H as Hourglass, G as Gauge, M as MessageSquare, C as Clock, Z as Zap, D as DollarSign, R as React, t as Markdown$1, u as rehypeHighlight, v as remarkGfm, w as defaultUrlTransform, T as Terminal, J as JsonView, x as vscodeTheme, y as tippy, z as reactDomExports, A as an, B as qt, Q as Qt, E as nn, F as parse, I as delegate, K as clientExports } from "./vendors-D2DMOduT.js";
|
|
2
2
|
const SOURCE_COLORS = {
|
|
3
3
|
scheduled: "#14b8a6",
|
|
4
4
|
cli: "#3b82f6",
|
|
@@ -34,13 +34,13 @@ function useLocalStorage(key, initial) {
|
|
|
34
34
|
}
|
|
35
35
|
function clearOutdatedLocalStorage() {
|
|
36
36
|
const version = localStorage.getItem(VERSION_KEY);
|
|
37
|
-
if (version !== "1.
|
|
38
|
-
const [MAJOR, MINOR, PATCH] = "1.
|
|
37
|
+
if (version !== "1.10.1") {
|
|
38
|
+
const [MAJOR, MINOR, PATCH] = "1.10.1".split(".");
|
|
39
39
|
const [major, minor, patch] = (version ?? "").split(".");
|
|
40
40
|
if (MAJOR !== major || MINOR !== minor) {
|
|
41
41
|
localStorage.clear();
|
|
42
42
|
}
|
|
43
|
-
localStorage.setItem(VERSION_KEY, "1.
|
|
43
|
+
localStorage.setItem(VERSION_KEY, "1.10.1");
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
function useMouseFontScale(storageKey) {
|
|
@@ -248,9 +248,6 @@ function WorkTimeOverlay({
|
|
|
248
248
|
const id = setInterval(() => setNow(Date.now()), 6e4);
|
|
249
249
|
return () => clearInterval(id);
|
|
250
250
|
}, []);
|
|
251
|
-
reactExports.useEffect(() => {
|
|
252
|
-
if (workTime) window.api.setWorkHours({ work_hours: { start: fmtHM(workTime.startMin), end: fmtHM(workTime.endMin) } });
|
|
253
|
-
}, [workTime]);
|
|
254
251
|
const chartRight = chartLeft + chartWidth;
|
|
255
252
|
const xForMin = (min) => chartLeft + (dayStart + min * 6e4 - viewStart) / (viewEnd - viewStart) * chartWidth;
|
|
256
253
|
const xStart = workTime ? xForMin(workTime.startMin) : 0;
|
|
@@ -259,15 +256,18 @@ function WorkTimeOverlay({
|
|
|
259
256
|
e.stopPropagation();
|
|
260
257
|
e.preventDefault();
|
|
261
258
|
setDragging(which);
|
|
259
|
+
let next = null;
|
|
262
260
|
const onMove = (m) => {
|
|
263
261
|
const rect = containerRef.current.getBoundingClientRect();
|
|
264
262
|
const ratio = clamp((m.clientX - rect.left - chartLeft) / chartWidth, 0, 1);
|
|
265
263
|
const ts = viewStart + ratio * (viewEnd - viewStart);
|
|
266
264
|
const min = clamp(Math.round((ts - dayStart) / 6e4 / SNAP_MIN) * SNAP_MIN, 0, 24 * 60);
|
|
267
|
-
|
|
265
|
+
next = which === "start" ? { ...workTime, startMin: Math.min(min, workTime.endMin - SNAP_MIN) } : { ...workTime, endMin: Math.max(min, workTime.startMin + SNAP_MIN) };
|
|
266
|
+
setWorkTime(next);
|
|
268
267
|
};
|
|
269
268
|
const onUp = () => {
|
|
270
269
|
setDragging(null);
|
|
270
|
+
if (next) window.api.setWorkHours({ work_hours: { start: fmtHM(next.startMin), end: fmtHM(next.endMin) } });
|
|
271
271
|
document.removeEventListener("mousemove", onMove);
|
|
272
272
|
document.removeEventListener("mouseup", onUp);
|
|
273
273
|
};
|
|
@@ -1189,6 +1189,7 @@ function SessionList({ sessions, selectedId, deepLink, onSelect }) {
|
|
|
1189
1189
|
const filtered = q ? sessions.filter((s) => [
|
|
1190
1190
|
s.customTitle,
|
|
1191
1191
|
s.agentName,
|
|
1192
|
+
s.tag,
|
|
1192
1193
|
s.aiTitle,
|
|
1193
1194
|
s.summary,
|
|
1194
1195
|
s.firstUserPrompt,
|
|
@@ -1296,6 +1297,10 @@ ${TIPS.time}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 11
|
|
|
1296
1297
|
] }) : sessionLabel(s) })
|
|
1297
1298
|
] }),
|
|
1298
1299
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "session-time-bottom", children: [
|
|
1300
|
+
s.tag && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "session-tag", title: "Session tag", children: [
|
|
1301
|
+
"#",
|
|
1302
|
+
s.tag
|
|
1303
|
+
] }),
|
|
1299
1304
|
s.project && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-project", title: s.project, children: s.projectShort }),
|
|
1300
1305
|
s.models?.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "session-model", children: s.models.map((m) => m.replace(/^claude-/, "")).join(", ") }),
|
|
1301
1306
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { title: format(s.lastActivityAt, "MMM d, HH:mm:ss"), children: fmtAgo(s.lastActivityAt) }),
|
|
@@ -1440,7 +1445,8 @@ function buildLinkComponents(basePath) {
|
|
|
1440
1445
|
}
|
|
1441
1446
|
};
|
|
1442
1447
|
}
|
|
1443
|
-
const
|
|
1448
|
+
const urlTransform = (url, key, node) => node.tagName === "img" && /^data:image\//i.test(url) ? url : defaultUrlTransform(url);
|
|
1449
|
+
const Chunk = React.memo(({ text, components }) => /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown$1, { remarkPlugins: [remarkGfm], rehypePlugins: [rehypeHighlight], components, urlTransform, children: text }));
|
|
1444
1450
|
function Markdown({ text, basePath = null, autoFence = false, className = "", ...rest }) {
|
|
1445
1451
|
const ref = reactExports.useRef(null);
|
|
1446
1452
|
const findOpen = useFindActive();
|
|
@@ -1748,7 +1754,7 @@ function InstructionFile({ it, model, tokens: tokens2 }) {
|
|
|
1748
1754
|
] }),
|
|
1749
1755
|
open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "aux-body", children: [
|
|
1750
1756
|
it.name && it.name !== it.file_path && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "instruction-path", children: it.file_path }),
|
|
1751
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { className: "block-text", text: it.content || "", basePath: it.file_path })
|
|
1757
|
+
it.tools ? it.tools.map((t) => /* @__PURE__ */ jsxRuntimeExports.jsx(InstructionFile, { it: { file_path: t.name, content: t.content }, model, tokens: countTokens(t.content, it.model ?? model) }, t.name)) : /* @__PURE__ */ jsxRuntimeExports.jsx(Markdown, { className: "block-text", text: it.content || "", basePath: it.file_path })
|
|
1752
1758
|
] })
|
|
1753
1759
|
] });
|
|
1754
1760
|
}
|
|
@@ -2087,7 +2093,8 @@ ${transcript}`;
|
|
|
2087
2093
|
- Model: ${meta.models.join(", ") || "—"}
|
|
2088
2094
|
- Service tier: ${meta.serviceTier || "—"}
|
|
2089
2095
|
- Speed: ${meta.speed ? meta.speed === "fast" ? meta.fastPricingUnknown ? "fast (pricing unknown)" : "fast" : meta.speed : "—"}
|
|
2090
|
-
|
|
2096
|
+
${meta.tag ? `- Tag: ${meta.tag}
|
|
2097
|
+
` : ""}- Git branch: ${meta.gitBranch || "—"}
|
|
2091
2098
|
- Source: ${meta.source || meta.entrypoint || "—"}
|
|
2092
2099
|
- Scheduled: ${meta.hasScheduledTask ? "yes" : "no"}
|
|
2093
2100
|
- CLI version: ${meta.version || "—"}
|
|
@@ -2332,6 +2339,7 @@ ${body}`);
|
|
|
2332
2339
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Model", value: modelLabel, mono: true }),
|
|
2333
2340
|
meta.serviceTier && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Service tier", value: meta.serviceTier }),
|
|
2334
2341
|
meta.speed && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Speed", value: meta.speed === "fast" ? meta.fastPricingUnknown ? "fast (pricing unknown)" : "fast" : meta.speed }),
|
|
2342
|
+
meta.tag && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Tag", value: meta.tag, mono: true }),
|
|
2335
2343
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Git branch", value: meta.gitBranch || "—", mono: true }),
|
|
2336
2344
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Source", value: meta.source || meta.entrypoint || "—" }),
|
|
2337
2345
|
meta.hasScheduledTask && /* @__PURE__ */ jsxRuntimeExports.jsx(Field, { label: "Scheduled", value: "yes" }),
|
|
@@ -54069,16 +54069,17 @@ var vscodeTheme = {
|
|
|
54069
54069
|
};
|
|
54070
54070
|
var reactDomExports = requireReactDom();
|
|
54071
54071
|
export {
|
|
54072
|
-
|
|
54073
|
-
|
|
54072
|
+
an as A,
|
|
54073
|
+
qt as B,
|
|
54074
54074
|
Clock as C,
|
|
54075
54075
|
DollarSign as D,
|
|
54076
|
-
|
|
54077
|
-
|
|
54076
|
+
nn as E,
|
|
54077
|
+
parse$1 as F,
|
|
54078
54078
|
Gauge as G,
|
|
54079
54079
|
Hourglass as H,
|
|
54080
|
-
|
|
54080
|
+
delegate as I,
|
|
54081
54081
|
JsonView as J,
|
|
54082
|
+
clientExports as K,
|
|
54082
54083
|
MessageSquare as M,
|
|
54083
54084
|
Qt as Q,
|
|
54084
54085
|
React as R,
|
|
@@ -54106,8 +54107,8 @@ export {
|
|
|
54106
54107
|
Markdown as t,
|
|
54107
54108
|
rehypeHighlight as u,
|
|
54108
54109
|
remarkGfm as v,
|
|
54109
|
-
|
|
54110
|
-
|
|
54111
|
-
|
|
54112
|
-
|
|
54110
|
+
defaultUrlTransform as w,
|
|
54111
|
+
vscodeTheme as x,
|
|
54112
|
+
tippy as y,
|
|
54113
|
+
reactDomExports as z
|
|
54113
54114
|
};
|
package/out/renderer/index.html
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
<meta charset="UTF-8" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>Discover</title>
|
|
9
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="./assets/vendors-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-FM6CSRHb.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="./assets/vendors-D2DMOduT.js">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="./assets/vendors-B4XMnGdr.css">
|
|
12
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
12
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DnYN00T3.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "claude-discover",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "Claude Discover — a desktop tool to browse your local Claude Code sessions: conversations, token costs, and captured API traffic",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"author": "dichev <dichev.lab@gmail.com>",
|
|
8
|
-
"repository": "github:dichev/claude-discover",
|
|
9
|
-
"engines": {
|
|
10
|
-
"node": ">=22"
|
|
11
|
-
},
|
|
12
|
-
"main": "out/main/main.js",
|
|
13
|
-
"bin": {
|
|
14
|
-
"claude-discover": "bin/claude-discover.mjs"
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"out",
|
|
18
|
-
"bin"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"dev": "electron-vite dev",
|
|
22
|
-
"build": "electron-vite build",
|
|
23
|
-
"start": "electron .",
|
|
24
|
-
"test": "vitest run --reporter=tree",
|
|
25
|
-
"proxy": "node bin/proxy.mjs --restart",
|
|
26
|
-
"readme": "gh markdown-preview",
|
|
27
|
-
"smoke": "npm pack && npx -y ./claude-discover-${npm_package_version}.tgz",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"@
|
|
35
|
-
"
|
|
36
|
-
"date-fns": "^4.4.0",
|
|
37
|
-
"electron": "43.2.0",
|
|
38
|
-
"electron-context-menu": "^4.1.2",
|
|
39
|
-
"electron-window-state": "^5.0.3",
|
|
40
|
-
"gpt-tokenizer": "^3.4.0",
|
|
41
|
-
"which": "^7.0.0",
|
|
42
|
-
"write-file-atomic": "^8.0.0"
|
|
43
|
-
},
|
|
44
|
-
"devDependencies": {
|
|
45
|
-
"@fontsource/inter": "^5.3.0",
|
|
46
|
-
"@fontsource/jetbrains-mono": "^5.3.0",
|
|
47
|
-
"@tomjs/electron-devtools-installer": "^4.0.1",
|
|
48
|
-
"@uiw/react-json-view": "^2.0.0-alpha.43",
|
|
49
|
-
"@vitejs/plugin-react": "^5.2.0",
|
|
50
|
-
"electron-vite": "^5.0.0",
|
|
51
|
-
"highlight.js": "^11.12.0",
|
|
52
|
-
"lucide-react": "^1.43.0",
|
|
53
|
-
"react": "^19.3.0",
|
|
54
|
-
"react-dom": "^19.3.0",
|
|
55
|
-
"react-markdown": "^10.1.0",
|
|
56
|
-
"react-resizable-panels": "^4.12.4",
|
|
57
|
-
"rehype-highlight": "^7.0.2",
|
|
58
|
-
"remark-gfm": "^4.0.1",
|
|
59
|
-
"tippy.js": "^6.3.7",
|
|
60
|
-
"vite": "^7.3.6",
|
|
61
|
-
"vitest": "^4.1.11"
|
|
62
|
-
},
|
|
63
|
-
"keywords": [
|
|
64
|
-
"claude",
|
|
65
|
-
"claude-code",
|
|
66
|
-
"claude-code-proxy",
|
|
67
|
-
"claude-code-inspect",
|
|
68
|
-
"system-prompt",
|
|
69
|
-
"session-browser",
|
|
70
|
-
"session-profiler",
|
|
71
|
-
"transcript-viewer",
|
|
72
|
-
"token-usage",
|
|
73
|
-
"token-cost",
|
|
74
|
-
"usage-dashboard",
|
|
75
|
-
"api-inspector"
|
|
76
|
-
]
|
|
77
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-discover",
|
|
3
|
+
"version": "1.10.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Claude Discover — a desktop tool to browse your local Claude Code sessions: conversations, token costs, and captured API traffic",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "dichev <dichev.lab@gmail.com>",
|
|
8
|
+
"repository": "github:dichev/claude-discover",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=22"
|
|
11
|
+
},
|
|
12
|
+
"main": "out/main/main.js",
|
|
13
|
+
"bin": {
|
|
14
|
+
"claude-discover": "bin/claude-discover.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"out",
|
|
18
|
+
"bin"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"dev": "electron-vite dev",
|
|
22
|
+
"build": "electron-vite build",
|
|
23
|
+
"start": "electron .",
|
|
24
|
+
"test": "vitest run --reporter=tree",
|
|
25
|
+
"proxy": "node bin/proxy.mjs --restart",
|
|
26
|
+
"readme": "gh markdown-preview",
|
|
27
|
+
"smoke:local": "npm pack && npx -y ./claude-discover-${npm_package_version}.tgz",
|
|
28
|
+
"smoke:global": "npm pack && npm i -g ./claude-discover-${npm_package_version}.tgz && claude-discover",
|
|
29
|
+
"postinstall": "install-electron",
|
|
30
|
+
"prepack": "npm run build",
|
|
31
|
+
"prepublishOnly": "npm whoami && npm test"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.266",
|
|
35
|
+
"@date-fns/tz": "^1.5.0",
|
|
36
|
+
"date-fns": "^4.4.0",
|
|
37
|
+
"electron": "43.2.0",
|
|
38
|
+
"electron-context-menu": "^4.1.2",
|
|
39
|
+
"electron-window-state": "^5.0.3",
|
|
40
|
+
"gpt-tokenizer": "^3.4.0",
|
|
41
|
+
"which": "^7.0.0",
|
|
42
|
+
"write-file-atomic": "^8.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@fontsource/inter": "^5.3.0",
|
|
46
|
+
"@fontsource/jetbrains-mono": "^5.3.0",
|
|
47
|
+
"@tomjs/electron-devtools-installer": "^4.0.1",
|
|
48
|
+
"@uiw/react-json-view": "^2.0.0-alpha.43",
|
|
49
|
+
"@vitejs/plugin-react": "^5.2.0",
|
|
50
|
+
"electron-vite": "^5.0.0",
|
|
51
|
+
"highlight.js": "^11.12.0",
|
|
52
|
+
"lucide-react": "^1.43.0",
|
|
53
|
+
"react": "^19.3.0",
|
|
54
|
+
"react-dom": "^19.3.0",
|
|
55
|
+
"react-markdown": "^10.1.0",
|
|
56
|
+
"react-resizable-panels": "^4.12.4",
|
|
57
|
+
"rehype-highlight": "^7.0.2",
|
|
58
|
+
"remark-gfm": "^4.0.1",
|
|
59
|
+
"tippy.js": "^6.3.7",
|
|
60
|
+
"vite": "^7.3.6",
|
|
61
|
+
"vitest": "^4.1.11"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"claude",
|
|
65
|
+
"claude-code",
|
|
66
|
+
"claude-code-proxy",
|
|
67
|
+
"claude-code-inspect",
|
|
68
|
+
"system-prompt",
|
|
69
|
+
"session-browser",
|
|
70
|
+
"session-profiler",
|
|
71
|
+
"transcript-viewer",
|
|
72
|
+
"token-usage",
|
|
73
|
+
"token-cost",
|
|
74
|
+
"usage-dashboard",
|
|
75
|
+
"api-inspector"
|
|
76
|
+
]
|
|
77
|
+
}
|